%w'bigdecimal date thread time uri'.each{|f| require f}
# Top level module for Sequel
#
# There are some module methods that are added via metaprogramming, one for
# each supported adapter. For example:
#
# DB = Sequel.sqlite # Memory database
# DB = Sequel.sqlite('blog.db')
# DB = Sequel.postgres('database_name', :user=>'user',
# :password=>'password', :host=>'host', :port=>5432,
# :max_connections=>10)
#
# If a block is given to these methods, it is passed the opened Database
# object, which is closed (disconnected) when the block exits, just
# like a block passed to connect. For example:
#
# Sequel.sqlite('blog.db'){|db| puts db[:users].count}
#
# Sequel currently adds methods to the Array, Hash, String and Symbol classes by
# default. You can either require 'sequel/no_core_ext' or set the
# +SEQUEL_NO_CORE_EXTENSIONS+ constant or environment variable before requiring
# sequel to have Sequel not add methods to those classes.
#
# For a more expanded introduction, see the {README}[link:files/README_rdoc.html].
# For a quicker introduction, see the {cheat sheet}[link:files/doc/cheat_sheet_rdoc.html].
module Sequel
@convert_two_digit_years = true
@datetime_class = Time
@empty_array_handle_nulls = true
@virtual_row_instance_eval = true
@require_thread = nil
# Mutex used to protect file loading/requireing
@require_mutex = Mutex.new
# Whether Sequel is being run in single threaded mode
@single_threaded = false
class << self
# Sequel converts two digit years in Dates and DateTimes by default,
# so 01/02/03 is interpreted at January 2nd, 2003, and 12/13/99 is interpreted
# as December 13, 1999. You can override this to treat those dates as
# January 2nd, 0003 and December 13, 0099, respectively, by:
#
# Sequel.convert_two_digit_years = false
attr_accessor :convert_two_digit_years
# Sequel can use either +Time+ or +DateTime+ for times returned from the
# database. It defaults to +Time+. To change it to +DateTime+:
#
# Sequel.datetime_class = DateTime
#
# For ruby versions less than 1.9.2, +Time+ has a limited range (1901 to
# 2038), so if you use datetimes out of that range, you need to switch
# to +DateTime+. Also, before 1.9.2, +Time+ can only handle local and UTC
# times, not other timezones. Note that +Time+ and +DateTime+ objects
# have a different API, and in cases where they implement the same methods,
# they often implement them differently (e.g. + using seconds on +Time+ and
# days on +DateTime+).
attr_accessor :datetime_class
# Sets whether or not to attempt to handle NULL values correctly when given
# an empty array. By default:
#
# DB[:a].filter(:b=>[])
# # SELECT * FROM a WHERE (b != b)
# DB[:a].exclude(:b=>[])
# # SELECT * FROM a WHERE (b = b)
#
# However, some databases (e.g. MySQL) will perform very poorly
# with this type of query. You can set this to false to get the
# following behavior:
#
# DB[:a].filter(:b=>[])
# # SELECT * FROM a WHERE 1 = 0
# DB[:a].exclude(:b=>[])
# # SELECT * FROM a WHERE 1 = 1
#
# This may not handle NULLs correctly, but can be much faster on
# some databases.
attr_accessor :empty_array_handle_nulls
# For backwards compatibility, has no effect.
attr_accessor :virtual_row_instance_eval
# Alias to the standard version of require
alias k_require require
private
# Make thread safe requiring reentrant to prevent deadlocks.
def check_requiring_thread
return yield if @single_threaded
t = Thread.current
return(yield) if @require_thread == t
@require_mutex.synchronize do
begin
@require_thread = t
yield
ensure
@require_thread = nil
end
end
end
end
# Returns true if the passed object could be a specifier of conditions, false otherwise.
# Currently, Sequel considers hashes and arrays of two element arrays as
# condition specifiers.
#
# Sequel.condition_specifier?({}) # => true
# Sequel.condition_specifier?([[1, 2]]) # => true
# Sequel.condition_specifier?([]) # => false
# Sequel.condition_specifier?([1]) # => false
# Sequel.condition_specifier?(1) # => false
def self.condition_specifier?(obj)
case obj
when Hash
true
when Array
!obj.empty? && !obj.is_a?(SQL::ValueList) && obj.all?{|i| (Array === i) && (i.length == 2)}
else
false
end
end
# Creates a new database object based on the supplied connection string
# and optional arguments. The specified scheme determines the database
# class used, and the rest of the string specifies the connection options.
# For example:
#
# DB = Sequel.connect('sqlite:/') # Memory database
# DB = Sequel.connect('sqlite://blog.db') # ./blog.db
# DB = Sequel.connect('sqlite:///blog.db') # /blog.db
# DB = Sequel.connect('postgres://user:password@host:port/database_name')
# DB = Sequel.connect('sqlite:///blog.db', :max_connections=>10)
#
# If a block is given, it is passed the opened +Database+ object, which is
# closed when the block exits. For example:
#
# Sequel.connect('sqlite://blog.db'){|db| puts db[:users].count}
#
# For details, see the {"Connecting to a Database" guide}[link:files/doc/opening_databases_rdoc.html].
# To set up a master/slave or sharded database connection, see the {"Master/Slave Databases and Sharding" guide}[link:files/doc/sharding_rdoc.html].
def self.connect(*args, &block)
Database.connect(*args, &block)
end
# Assume the core extensions are not loaded by default, if the core_extensions
# extension is loaded, this will be overridden.
def self.core_extensions?
false
end
# Convert the +exception+ to the given class. The given class should be
# Sequel::Error or a subclass. Returns an instance of +klass+ with
# the message and backtrace of +exception+.
def self.convert_exception_class(exception, klass)
return exception if exception.is_a?(klass)
e = klass.new("#{exception.class}: #{exception.message}")
e.wrapped_exception = exception
e.set_backtrace(exception.backtrace)
e
end
# Load all Sequel extensions given. Extensions are just files that exist under
# sequel/extensions in the load path, and are just required. Generally,
# extensions modify the behavior of +Database+ and/or +Dataset+, but Sequel ships
# with some extensions that modify other classes that exist for backwards compatibility.
# In some cases, requiring an extension modifies classes directly, and in others,
# it just loads a module that you can extend other classes with. Consult the documentation
# for each extension you plan on using for usage.
#
# Sequel.extension(:schema_dumper)
# Sequel.extension(:pagination, :query)
def self.extension(*extensions)
extensions.each{|e| tsk_require "sequel/extensions/#{e}"}
end
# Set the method to call on identifiers going into the database. This affects
# the literalization of identifiers by calling this method on them before they are input.
# Sequel upcases identifiers in all SQL strings for most databases, so to turn that off:
#
# Sequel.identifier_input_method = nil
#
# to downcase instead:
#
# Sequel.identifier_input_method = :downcase
#
# Other String instance methods work as well.
def self.identifier_input_method=(value)
Database.identifier_input_method = value
end
# Set the method to call on identifiers coming out of the database. This affects
# the literalization of identifiers by calling this method on them when they are
# retrieved from the database. Sequel downcases identifiers retrieved for most
# databases, so to turn that off:
#
# Sequel.identifier_output_method = nil
#
# to upcase instead:
#
# Sequel.identifier_output_method = :upcase
#
# Other String instance methods work as well.
def self.identifier_output_method=(value)
Database.identifier_output_method = value
end
# Parse the string as JSON and return the result.
# This is solely for internal use, it should not be used externally.
def self.parse_json(json) # :nodoc:
JSON.parse(json, :create_additions=>false)
end
# Set whether to quote identifiers for all databases by default. By default,
# Sequel quotes identifiers in all SQL strings, so to turn that off:
#
# Sequel.quote_identifiers = false
def self.quote_identifiers=(value)
Database.quote_identifiers = value
end
# Convert each item in the array to the correct type, handling multi-dimensional
# arrays. For each element in the array or subarrays, call the converter,
# unless the value is nil.
def self.recursive_map(array, converter)
array.map do |i|
if i.is_a?(Array)
recursive_map(i, converter)
elsif i
converter.call(i)
end
end
end
# Require all given +files+ which should be in the same or a subdirectory of
# this file. If a +subdir+ is given, assume all +files+ are in that subdir.
# This is used to ensure that the files loaded are from the same version of
# Sequel as this file.
def self.require(files, subdir=nil)
Array(files).each{|f| super("#{File.dirname(__FILE__).untaint}/#{"#{subdir}/" if subdir}#{f}")}
end
# Set whether Sequel is being used in single threaded mode. By default,
# Sequel uses a thread-safe connection pool, which isn't as fast as the
# single threaded connection pool, and also has some additional thread
# safety checks. If your program will only have one thread,
# and speed is a priority, you should set this to true:
#
# Sequel.single_threaded = true
def self.single_threaded=(value)
@single_threaded = value
Database.single_threaded = value
end
COLUMN_REF_RE1 = /\A((?:(?!__).)+)__((?:(?!___).)+)___(.+)\z/.freeze
COLUMN_REF_RE2 = /\A((?:(?!___).)+)___(.+)\z/.freeze
COLUMN_REF_RE3 = /\A((?:(?!__).)+)__(.+)\z/.freeze
# Splits the symbol into three parts. Each part will
# either be a string or nil.
#
# For columns, these parts are the table, column, and alias.
# For tables, these parts are the schema, table, and alias.
def self.split_symbol(sym)
case s = sym.to_s
when COLUMN_REF_RE1
[$1, $2, $3]
when COLUMN_REF_RE2
[nil, $1, $2]
when COLUMN_REF_RE3
[$1, $2, nil]
else
[nil, s, nil]
end
end
# Converts the given +string+ into a +Date+ object.
#
# Sequel.string_to_date('2010-09-10') # Date.civil(2010, 09, 10)
def self.string_to_date(string)
begin
Date.parse(string, Sequel.convert_two_digit_years)
rescue => e
raise convert_exception_class(e, InvalidValue)
end
end
# Converts the given +string+ into a +Time+ or +DateTime+ object, depending on the
# value of Sequel.datetime_class.
#
# Sequel.string_to_datetime('2010-09-10 10:20:30') # Time.local(2010, 09, 10, 10, 20, 30)
def self.string_to_datetime(string)
begin
if datetime_class == DateTime
DateTime.parse(string, convert_two_digit_years)
else
datetime_class.parse(string)
end
rescue => e
raise convert_exception_class(e, InvalidValue)
end
end
# Converts the given +string+ into a Sequel::SQLTime object.
#
# v = Sequel.string_to_time('10:20:30') # Sequel::SQLTime.parse('10:20:30')
# DB.literal(v) # => '10:20:30'
def self.string_to_time(string)
begin
SQLTime.parse(string)
rescue => e
raise convert_exception_class(e, InvalidValue)
end
end
if defined?(RUBY_ENGINE) && RUBY_ENGINE != 'ruby'
# :nocov:
# Mutex used to protect mutable data structures
@data_mutex = Mutex.new
# Unless in single threaded mode, protects access to any mutable
# global data structure in Sequel.
# Uses a non-reentrant mutex, so calling code should be careful.
def self.synchronize(&block)
@single_threaded ? yield : @data_mutex.synchronize(&block)
end
# :nocov:
else
# Yield directly to the block. You don't need to synchronize
# access on MRI because the GVL makes certain methods atomic.
def self.synchronize
yield
end
end
# Uses a transaction on all given databases with the given options. This:
#
# Sequel.transaction([DB1, DB2, DB3]){...}
#
# is equivalent to:
#
# DB1.transaction do
# DB2.transaction do
# DB3.transaction do
# ...
# end
# end
# end
#
# except that if Sequel::Rollback is raised by the block, the transaction is
# rolled back on all databases instead of just the last one.
#
# Note that this method cannot guarantee that all databases will commit or
# rollback. For example, if DB3 commits but attempting to commit on DB2
# fails (maybe because foreign key checks are deferred), there is no way
# to uncommit the changes on DB3. For that kind of support, you need to
# have two-phase commit/prepared transactions (which Sequel supports on
# some databases).
def self.transaction(dbs, opts={}, &block)
unless opts[:rollback]
rescue_rollback = true
opts = opts.merge(:rollback=>:reraise)
end
pr = dbs.reverse.inject(block){|bl, db| proc{db.transaction(opts, &bl)}}
if rescue_rollback
begin
pr.call
rescue Sequel::Rollback
nil
end
else
pr.call
end
end
# Same as Sequel.require, but wrapped in a mutex in order to be thread safe.
def self.ts_require(*args)
check_requiring_thread{require(*args)}
end
# Same as Kernel.require, but wrapped in a mutex in order to be thread safe.
def self.tsk_require(*args)
check_requiring_thread{k_require(*args)}
end
# If the supplied block takes a single argument,
# yield an SQL::VirtualRow instance to the block
# argument. Otherwise, evaluate the block in the context of a
# SQL::VirtualRow instance.
#
# Sequel.virtual_row{a} # Sequel::SQL::Identifier.new(:a)
# Sequel.virtual_row{|o| o.a{}} # Sequel::SQL::Function.new(:a)
def self.virtual_row(&block)
vr = VIRTUAL_ROW
case block.arity
when -1, 0
vr.instance_exec(&block)
else
block.call(vr)
end
end
### Private Class Methods ###
# Helper method that the database adapter class methods that are added to Sequel via
# metaprogramming use to parse arguments.
def self.adapter_method(adapter, *args, &block) # :nodoc:
raise(::Sequel::Error, "Wrong number of arguments, 0-2 arguments valid") if args.length > 2
opts = {:adapter=>adapter.to_sym}
opts[:database] = args.shift if args.length >= 1 && !(args[0].is_a?(Hash))
if Hash === (arg = args[0])
opts.merge!(arg)
elsif !arg.nil?
raise ::Sequel::Error, "Wrong format of arguments, either use (), (String), (Hash), or (String, Hash)"
end
connect(opts, &block)
end
# Method that adds a database adapter class method to Sequel that calls
# Sequel.adapter_method.
#
# Do not call this method with untrusted input, as that can result in
# arbitrary code execution.
def self.def_adapter_method(*adapters) # :nodoc:
adapters.each do |adapter|
instance_eval("def #{adapter}(*args, &block); adapter_method('#{adapter}', *args, &block) end", __FILE__, __LINE__)
end
end
private_class_method :adapter_method, :def_adapter_method
require(%w"sql connection_pool exceptions dataset database timezones ast_transformer version")
if !defined?(::SEQUEL_NO_CORE_EXTENSIONS) && !ENV.has_key?('SEQUEL_NO_CORE_EXTENSIONS')
# :nocov:
extension(:core_extensions)
# :nocov:
end
# Add the database adapter class methods to Sequel via metaprogramming
def_adapter_method(*Database::ADAPTERS)
end