# frozen-string-literal: true %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 Sequel.connect. For example: # # Sequel.sqlite('blog.db'){|db| puts db[:users].count} # # For a more expanded introduction, see the {README}[rdoc-ref:README.rdoc]. # For a quicker introduction, see the {cheat sheet}[rdoc-ref:doc/cheat_sheet.rdoc]. module Sequel @convert_two_digit_years = true @datetime_class = Time @split_symbols = false @single_threaded = false # Mutex used to protect mutable data structures @data_mutex = Mutex.new # Frozen hash used as the default options hash for most options. OPTS = {}.freeze SPLIT_SYMBOL_CACHE = {} module SequelMethods # 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 # # 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 # 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 attr_accessor :single_threaded # Alias of original require method, as Sequel.require is does a relative # require for backwards compatibility. alias orig_require require private :orig_require # 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 condition_specifier?(obj) case obj when Hash true when Array !obj.empty? && !obj.is_a?(SQL::ValueList) && obj.all?{|i| i.is_a?(Array) && (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) # # You can also pass a single options hash: # # DB = Sequel.connect(adapter: 'sqlite', database: './blog.db') # # 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} # # If a block is not given, a reference to this database will be held in # Sequel::DATABASES until it is removed manually. This is by # design, and used by Sequel::Model to pick the default # database. It is recommended to pass a block if you do not want the # resulting Database object to remain in memory until the process # terminates, or use the keep_reference: false Database option. # # For details, see the {"Connecting to a Database" guide}[rdoc-ref:doc/opening_databases.rdoc]. # To set up a primary/replica or sharded database connection, see the {"Primary/Replica Database Configurations and Sharding" guide}[rdoc-ref:doc/sharding.rdoc]. def 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 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 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 # The current concurrency primitive, Thread.current by default. def current Thread.current end # Load all Sequel extensions given. Extensions are just files that exist under # sequel/extensions in the load path, and are just required. # 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(:blank) # Sequel.extension(:core_extensions, :named_timezones) def extension(*extensions) extensions.each{|e| orig_require("sequel/extensions/#{e}")} end # The exception classed raised if there is an error parsing JSON. # This can be overridden to use an alternative json implementation. def json_parser_error_class JSON::ParserError end # Convert given object to json and return the result. # This can be overridden to use an alternative json implementation. def object_to_json(obj, *args, &block) obj.to_json(*args, &block) end # Parse the string as JSON and return the result. # This can be overridden to use an alternative json implementation. def parse_json(json) JSON.parse(json, :create_additions=>false) 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 recursive_map(array, converter) array.map do |i| if i.is_a?(Array) recursive_map(i, converter) elsif !i.nil? converter.call(i) end end end # For backwards compatibility only. require_relative should be used instead. def require(files, subdir=nil) # Use Kernel.require_relative to work around JRuby 9.0 bug Array(files).each{|f| Kernel.require_relative "#{"#{subdir}/" if subdir}#{f}"} end # Splits the symbol into three parts, if symbol splitting is enabled (not the default). # Each part will either be a string or nil. If symbol splitting # is disabled, returns an array with the first and third parts # being nil, and the second part beind a string version of the symbol. # # For columns, these parts are the table, column, and alias. # For tables, these parts are the schema, table, and alias. def split_symbol(sym) unless v = Sequel.synchronize{SPLIT_SYMBOL_CACHE[sym]} if split_symbols? v = case s = sym.to_s when /\A((?:(?!__).)+)__((?:(?!___).)+)___(.+)\z/ [$1.freeze, $2.freeze, $3.freeze].freeze when /\A((?:(?!___).)+)___(.+)\z/ [nil, $1.freeze, $2.freeze].freeze when /\A((?:(?!__).)+)__(.+)\z/ [$1.freeze, $2.freeze, nil].freeze else [nil, s.freeze, nil].freeze end else v = [nil,sym.to_s.freeze,nil].freeze end Sequel.synchronize{SPLIT_SYMBOL_CACHE[sym] = v} end v end # Setting this to true enables Sequel's historical behavior of splitting # symbols on double or triple underscores: # # :table__column # table.column # :column___alias # column AS alias # :table__column___alias # table.column AS alias # # It is only recommended to turn this on for backwards compatibility until # such symbols have been converted to use newer Sequel APIs such as: # # Sequel[:table][:column] # table.column # Sequel[:column].as(:alias) # column AS alias # Sequel[:table][:column].as(:alias) # table.column AS alias # # Sequel::Database instances do their own caching of literalized # symbols, and changing this setting does not affect those caches. It is # recommended that if you want to change this setting, you do so directly # after requiring Sequel, before creating any Sequel::Database instances. # # Disabling symbol splitting will also disable the handling # of double underscores in virtual row methods, causing such methods to # yield regular identifers instead of qualified identifiers: # # # Sequel.split_symbols = true # Sequel.expr{table__column} # table.column # Sequel.expr{table[:column]} # table.column # # # Sequel.split_symbols = false # Sequel.expr{table__column} # table__column # Sequel.expr{table[:column]} # table.column def split_symbols=(v) Sequel.synchronize{SPLIT_SYMBOL_CACHE.clear} @split_symbols = v end # Whether Sequel currently splits symbols into qualified/aliased identifiers. def split_symbols? @split_symbols end # Converts the given +string+ into a +Date+ object. # # Sequel.string_to_date('2010-09-10') # Date.civil(2010, 09, 10) def 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 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 string_to_time(string) begin SQLTime.parse(string) rescue => e raise convert_exception_class(e, InvalidValue) end end # 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. # In general, this should only be used around the minimal possible code # such as Hash#[], Hash#[]=, Hash#delete, Array#<<, and Array#delete. def synchronize(&block) @single_threaded ? yield : @data_mutex.synchronize(&block) end if RUBY_VERSION >= '2.1' # A timer object that can be passed to Sequel.elapsed_seconds_since # to return the number of seconds elapsed. def start_timer Process.clock_gettime(Process::CLOCK_MONOTONIC) end else # :nocov: def start_timer # :nodoc: Time.now end # :nocov: end # The elapsed seconds since the given timer object was created. The # timer object should have been created via Sequel.start_timer. def elapsed_seconds_since(timer) start_timer - timer 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 transaction(dbs, opts=OPTS, &block) unless opts[:rollback] rescue_rollback = true opts = Hash[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 # 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 virtual_row(&block) vr = VIRTUAL_ROW case block.arity when -1, 0 vr.instance_exec(&block) else block.call(vr) end end private # Helper method that the database adapter class methods that are added to Sequel via # metaprogramming use to parse arguments. def adapter_method(adapter, *args, &block) options = args.last.is_a?(Hash) ? args.pop : OPTS opts = {:adapter => adapter.to_sym} opts[:database] = args.shift if args.first.is_a?(String) if args.any? raise ::Sequel::Error, "Wrong format of arguments, either use (), (String), (Hash), or (String, Hash)" end connect(opts.merge(options), &block) end # Method that adds a database adapter class method to Sequel that calls # Sequel.adapter_method. def def_adapter_method(*adapters) # :nodoc: adapters.each do |adapter| define_singleton_method(adapter){|*args, &block| adapter_method(adapter, *args, &block)} end end end extend SequelMethods require_relative "deprecated" require_relative "sql" require_relative "connection_pool" require_relative "exceptions" require_relative "dataset" require_relative "database" require_relative "timezones" require_relative "ast_transformer" require_relative "version" class << self # Allow nicer syntax for creating Sequel expressions: # # Sequel[1] # => Sequel::SQL::NumericExpression: 1 # Sequel["a"] # => Sequel::SQL::StringExpression: 'a' # Sequel[:a] # => Sequel::SQL::Identifier: "a" # Sequel[a: 1] # => Sequel::SQL::BooleanExpression: ("a" = 1) alias_method :[], :expr end # Add the database adapter class methods to Sequel via metaprogramming def_adapter_method(*Database::ADAPTERS) end