sequel 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG ADDED
@@ -0,0 +1,13 @@
1
+ *0.0.1*
2
+
3
+ * More documentation for Dataset.
4
+
5
+ * Renamed Database#query to Database#dataset.
6
+
7
+ * Added Dataset#insert_multiple for inserting multiple records.
8
+
9
+ * Added Dataset#<< as shorthand for inserting records.
10
+
11
+ * Added Database#<< method for executing arbitrary SQL.
12
+
13
+ * Imported Sequel code.
data/COPYING ADDED
@@ -0,0 +1,18 @@
1
+ Copyright (c) 2006 Sharon Rosner
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to
5
+ deal in the Software without restriction, including without limitation the
6
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7
+ sell copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16
+ THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,151 @@
1
+ == about Sequel
2
+
3
+ Sequel is an ORM framework for Ruby. Sequel provides thread safety, connection pooling, and a DSL for constructing queries and table schemas.
4
+
5
+ == Sequel vs. ActiveRecord
6
+
7
+ Sequel offers the following advantages over ActiveRecord:
8
+
9
+ * Better performance with large tables: unlike ActiveRecord, Sequel does not load the entire resultset into memory, but fetches each record separately and implements an Enumerable interface.
10
+ * Easy construction of queries using a DSL.
11
+ * Using model classes is possible, but not mandatory.
12
+
13
+ == Resources
14
+
15
+ * {Project page}[http://code.google.com/p/ruby-sequel/]
16
+ * {Source code}[http://ruby-sequel.googlecode.com/svn/]
17
+ * {Bug tracking}[http://code.google.com/p/ruby-sequel/issues/list]
18
+ * {RubyForge page}[http://rubyforge.org/projects/sequel/]
19
+
20
+ To check out the source code:
21
+
22
+ svn co http://ruby-sequel.googlecode.com/svn/trunk
23
+
24
+ == Installation
25
+
26
+ sudo gem install sequel
27
+
28
+ == A Short Tutorial
29
+
30
+ === Connecting to a database
31
+
32
+ There are two ways to create a connection to a database. The easier way is to provide a connection URL:
33
+
34
+ DB = Sequel.connect("postgres://postgres:postgres@localhost:5432/my_db")
35
+
36
+ You can also specify optional parameters, such as the connection pool size:
37
+
38
+ DB = Sequel.connect("postgres://postgres:postgres@localhost:5432/my_db",
39
+ :max_connections => 10)
40
+
41
+ The second, more verbose, way is to create an instance of a database class:
42
+
43
+ DB = Sequel::Postgres::Database.new(:database => 'my_db', :host => 'localhost',
44
+ :port => 5432)
45
+
46
+ == Arbitrary SQL queries
47
+
48
+ DB.execute("create table t (a text, b text)")
49
+ DB.execute("insert into t values ('a', 'b')")
50
+
51
+ Or more succintly:
52
+
53
+ DB << "create table t (a text, b text)"
54
+ DB << "insert into t values ('a', 'b')"
55
+
56
+ === Creating Datasets
57
+
58
+ Dataset is the primary means through which records are retrieved and manipulated. You can create an blank dataset by using the query method:
59
+
60
+ dataset = DB.query
61
+
62
+ Or by using the from methods:
63
+
64
+ posts = DB.from(:posts)
65
+
66
+ You can also use the equivalent shorthand:
67
+
68
+ posts = DB[:posts]
69
+
70
+ Note: the dataset will only fetch records when you explicitly ask for them, as will be shown below. Datasets can be manipulated to filter through records, change record order and even join tables, as will also be shown below.
71
+
72
+ === Retrieving Records
73
+
74
+ You can retrieve records by using the all method:
75
+
76
+ posts.all
77
+
78
+ The all method returns an array of hashes, where each hash corresponds to a record.
79
+
80
+ You can also iterate through records one at a time:
81
+
82
+ posts.each {|row| p row}
83
+
84
+ Or perform more advanced stuff:
85
+
86
+ posts.map(:id)
87
+ posts.inject({}) {|h, r| h[r[:id]] = r[:name]}
88
+
89
+ You can also retrieve the first record in a dataset:
90
+
91
+ posts.first
92
+
93
+ If the dataset is ordered, you can also ask for the last record:
94
+
95
+ posts.order(:stamp).last
96
+
97
+ === Filtering Records
98
+
99
+ The simplest way to filter records is to provide a hash of values to match:
100
+
101
+ my_posts = posts.filter(:category => 'ruby', :author => 'david')
102
+
103
+ You can also specify ranges:
104
+
105
+ my_posts = posts.filter(:stamp => 2.weeks.ago..1.week.ago)
106
+
107
+ Some adapters will also let you specify Regexps:
108
+
109
+ my_posts = posts.filter(:category => /ruby/i)
110
+
111
+ You can also use an inverse filter:
112
+
113
+ my_posts = posts.exclude(:category => /ruby/i)
114
+
115
+ You can then retrieve the records by using any of the retrieval methods:
116
+
117
+ my_posts.each {|row| p row}
118
+
119
+ You can also specify a custom WHERE clause:
120
+
121
+ posts.filter('(stamp < ?) AND (author <> ?)', 3.days.ago, author_name)
122
+
123
+ === Counting Records
124
+
125
+ posts.count
126
+
127
+ === Ordering Records
128
+
129
+ posts.order(:stamp)
130
+
131
+ You can also specify descending order
132
+
133
+ posts.order(:stamp.DESC)
134
+
135
+ === Deleting Records
136
+
137
+ posts.filter('stamp < ?', 3.days.ago).delete
138
+
139
+ === Inserting Records
140
+
141
+ posts.insert(:category => 'ruby', :author => 'david')
142
+
143
+ Or alternatively:
144
+
145
+ posts << {:category => 'ruby', :author => 'david'}
146
+
147
+ === Updating Records
148
+
149
+ posts.filter('stamp < ?', 3.days.ago).update(:state => 'archived')
150
+
151
+ ===
data/Rakefile ADDED
@@ -0,0 +1,96 @@
1
+ require 'rake'
2
+ require 'rake/clean'
3
+ require 'rake/gempackagetask'
4
+ require 'rake/rdoctask'
5
+ require 'fileutils'
6
+ include FileUtils
7
+
8
+ NAME = "sequel"
9
+ VERS = "0.0.1"
10
+ CLEAN.include ['**/.*.sw?', 'pkg/*', '.config', 'doc/*', 'coverage/*']
11
+ RDOC_OPTS = ['--quiet', '--title', "Sequel Documentation",
12
+ "--opname", "index.html",
13
+ "--line-numbers",
14
+ "--main", "README",
15
+ "--inline-source"]
16
+
17
+ desc "Packages up Sequel."
18
+ task :default => [:package]
19
+ task :package => [:clean]
20
+
21
+ task :doc => [:rdoc]
22
+
23
+ Rake::RDocTask.new do |rdoc|
24
+ rdoc.rdoc_dir = 'doc/rdoc'
25
+ rdoc.options += RDOC_OPTS
26
+ rdoc.main = "README"
27
+ rdoc.title = "Sequel Documentation"
28
+ rdoc.rdoc_files.add ['README', 'COPYING', 'lib/sequel.rb', 'lib/sequel/**/*.rb']
29
+ end
30
+
31
+ spec = Gem::Specification.new do |s|
32
+ s.name = NAME
33
+ s.version = VERS
34
+ s.platform = Gem::Platform::RUBY
35
+ s.has_rdoc = true
36
+ s.extra_rdoc_files = ["README", "CHANGELOG", "COPYING"]
37
+ s.rdoc_options += RDOC_OPTS +
38
+ ['--exclude', '^(examples|extras)\/', '--exclude', 'lib/sequel.rb']
39
+ s.summary = "ORM framework for Ruby."
40
+ s.description = s.summary
41
+ s.author = "Sharon Rosner"
42
+ s.email = 'ciconia@gmail.com'
43
+ s.homepage = 'http://code.google.com/p/ruby-sequel/'
44
+
45
+ s.add_dependency('metaid')
46
+ s.required_ruby_version = '>= 1.8.2'
47
+
48
+ s.files = %w(COPYING README Rakefile) + Dir.glob("{doc,spec,lib}/**/*")
49
+
50
+ s.require_path = "lib"
51
+ end
52
+
53
+ Rake::GemPackageTask.new(spec) do |p|
54
+ p.need_tar = true
55
+ p.gem_spec = spec
56
+ end
57
+
58
+ task :install do
59
+ sh %{rake package}
60
+ sh %{sudo gem install pkg/#{NAME}-#{VERS}}
61
+ end
62
+
63
+ task :uninstall => [:clean] do
64
+ sh %{sudo gem uninstall #{NAME}}
65
+ end
66
+
67
+ desc 'Update docs and upload to rubyforge.org'
68
+ task :doc_rforge do
69
+ sh %{rake doc}
70
+ sh %{scp -r doc/rdoc/* ciconia@rubyforge.org:/var/www/gforge-projects/sequel}
71
+ end
72
+
73
+ require 'spec/rake/spectask'
74
+
75
+ desc "Run specs with coverage"
76
+ Spec::Rake::SpecTask.new('spec') do |t|
77
+ t.spec_files = FileList['spec/*_spec.rb']
78
+ t.rcov = true
79
+ end
80
+
81
+ ##############################################################################
82
+ # Statistics
83
+ ##############################################################################
84
+
85
+ STATS_DIRECTORIES = [
86
+ %w(Code lib/),
87
+ %w(Spec spec/)
88
+ ].collect { |name, dir| [ name, "./#{dir}" ] }.select { |name, dir| File.directory?(dir) }
89
+
90
+ desc "Report code statistics (KLOCs, etc) from the application"
91
+ task :stats do
92
+ require 'extra/stats'
93
+ verbose = true
94
+ CodeStatistics.new(*STATS_DIRECTORIES).to_s
95
+ end
96
+
data/lib/sequel.rb ADDED
@@ -0,0 +1,13 @@
1
+ dir = File.join(File.dirname(__FILE__), 'sequel')
2
+ require File.join(dir, 'core_ext')
3
+ require File.join(dir, 'database')
4
+ require File.join(dir, 'connection_pool')
5
+ require File.join(dir, 'schema')
6
+ require File.join(dir, 'dataset')
7
+ require File.join(dir, 'model')
8
+
9
+ module Sequel #:nodoc:
10
+ def self.connect(url)
11
+ Database.connect(url)
12
+ end
13
+ end
@@ -0,0 +1,65 @@
1
+ require 'thread'
2
+
3
+ module Sequel
4
+ class ConnectionPool
5
+ attr_reader :max_size, :mutex, :conn_maker
6
+ attr_reader :available_connections, :allocated, :created_count
7
+
8
+ def initialize(max_size = 4, &block)
9
+ @max_size = max_size
10
+ @mutex = Mutex.new
11
+ @conn_maker = block
12
+
13
+ @available_connections = []
14
+ @allocated = {}
15
+ @created_count = 0
16
+ end
17
+
18
+ def size
19
+ @created_count
20
+ end
21
+
22
+ def hold
23
+ t = Thread.current
24
+ if (conn = owned_connection(t))
25
+ return yield(conn)
26
+ end
27
+ while !(conn = acquire(t))
28
+ sleep 0.001
29
+ end
30
+ begin
31
+ yield conn
32
+ ensure
33
+ release(t)
34
+ end
35
+ end
36
+
37
+ def owned_connection(thread)
38
+ @mutex.synchronize {@allocated[thread]}
39
+ end
40
+
41
+ def acquire(thread)
42
+ @mutex.synchronize do
43
+ @allocated[thread] ||= available
44
+ end
45
+ end
46
+
47
+ def available
48
+ @available_connections.pop || make_new
49
+ end
50
+
51
+ def make_new
52
+ if @created_count < @max_size
53
+ @created_count += 1
54
+ @conn_maker.call
55
+ end
56
+ end
57
+
58
+ def release(thread)
59
+ @mutex.synchronize do
60
+ @available_connections << @allocated[thread]
61
+ @allocated.delete(thread)
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,9 @@
1
+ # Time extensions.
2
+ class Time
3
+ SQL_FORMAT = "TIMESTAMP '%Y-%m-%d %H:%M:%S'".freeze
4
+
5
+ # Formats the Time object as an SQL TIMESTAMP.
6
+ def to_sql_timestamp
7
+ strftime(SQL_FORMAT)
8
+ end
9
+ end
@@ -0,0 +1,119 @@
1
+ require 'uri'
2
+
3
+ require File.join(File.dirname(__FILE__), 'schema')
4
+
5
+ module Sequel
6
+ # A Database object represents a virtual connection to a database.
7
+ # The Database class is meant to be subclassed by database adapters in order
8
+ # to provide the functionality needed for executing queries.
9
+ class Database
10
+ # Constructs a new instance of a database connection with the specified
11
+ # options hash.
12
+ #
13
+ # Sequel::Database is an abstract class that is not useful by itself.
14
+ def initialize(opts = {})
15
+ @opts = opts
16
+ end
17
+
18
+ # Returns a new dataset with the from method invoked.
19
+ def from(*args); dataset.from(*args); end
20
+
21
+ # Returns a new dataset with the select method invoked.
22
+ def select(*args); dataset.select(*args); end
23
+
24
+ # Returns a new dataset with the from parameter set. For example,
25
+ # db[:posts].each {|p| puts p[:title]}
26
+ def [](table)
27
+ dataset.from(table)
28
+ end
29
+
30
+ # call-seq:
31
+ # db.execute(sql)
32
+ # db << sql
33
+ #
34
+ # Executes an sql query.
35
+ def <<(sql)
36
+ execute(sql)
37
+ end
38
+
39
+ # Returns a literal SQL representation of a value. This method is usually
40
+ # overriden in database adapters.
41
+ def literal(v)
42
+ case v
43
+ when String: "'%s'" % v
44
+ else v.to_s
45
+ end
46
+ end
47
+
48
+ # Creates a table. The easiest way to use this method is to provide a
49
+ # block:
50
+ # DB.create_table :posts do
51
+ # primary_key :id, :serial
52
+ # column :title, :text
53
+ # column :content, :text
54
+ # index :title
55
+ # end
56
+ def create_table(name, columns = nil, indexes = nil, &block)
57
+ if block
58
+ schema = Schema.new
59
+ schema.create_table(name, &block)
60
+ schema.create(self)
61
+ else
62
+ execute Schema.create_table_sql(name, columns, indexes)
63
+ end
64
+ end
65
+
66
+ # Drops a table.
67
+ def drop_table(name)
68
+ execute Schema.drop_table_sql(name)
69
+ end
70
+
71
+ # Performs a brute-force check for the existance of a table. This method is
72
+ # usually overriden in descendants.
73
+ def table_exists?(name)
74
+ from(name).first && true
75
+ rescue
76
+ false
77
+ end
78
+
79
+ @@adapters = Hash.new
80
+
81
+ # Sets the adapter scheme for the database class. Call this method in
82
+ # descendnants of Database to allow connection using a URL. For example:
83
+ # class DB2::Database < Sequel::Database
84
+ # set_adapter_scheme :db2
85
+ # ...
86
+ # end
87
+ def self.set_adapter_scheme(scheme)
88
+ @@adapters[scheme.to_sym] = self
89
+ end
90
+
91
+ # Converts a uri to an options hash. These options are then passed
92
+ # to a newly created database object.
93
+ def self.uri_to_options(uri)
94
+ {
95
+ :user => uri.user,
96
+ :password => uri.password,
97
+ :host => uri.host,
98
+ :port => uri.port,
99
+ :database => (uri.path =~ /\/(.*)/) && ($1)
100
+ }
101
+ end
102
+
103
+ # call-seq:
104
+ # Sequel::Database.connect(conn_string)
105
+ # Sequel.connect(conn_string)
106
+ #
107
+ # Creates a new database object based on the supplied connection string.
108
+ # The specified scheme determines the database class used, and the rest
109
+ # of the string specifies the connection options. For example:
110
+ # DB = Sequel.connect('sqlite:///blog.db')
111
+ def self.connect(conn_string)
112
+ uri = URI.parse(conn_string)
113
+ c = @@adapters[uri.scheme.to_sym]
114
+ raise "Invalid database scheme" unless c
115
+ c.new(c.uri_to_options(uri))
116
+ end
117
+ end
118
+ end
119
+