# frozen-string-literal: true module Sequel class Dataset # --------------------- # :section: 3 - User Methods relating to SQL Creation # These are methods you can call to see what SQL will be generated by the dataset. # --------------------- # Returns an EXISTS clause for the dataset as an SQL::PlaceholderLiteralString. # # DB.select(1).where(DB[:items].exists) # # SELECT 1 WHERE (EXISTS (SELECT * FROM items)) def exists SQL::PlaceholderLiteralString.new(EXISTS, [self], true) end # Returns an INSERT SQL query string. See +insert+. # # DB[:items].insert_sql(:a=>1) # # => "INSERT INTO items (a) VALUES (1)" def insert_sql(*values) return static_sql(@opts[:sql]) if @opts[:sql] check_modification_allowed! columns = [] case values.size when 0 return insert_sql({}) when 1 case vals = values[0] when Hash values = [] vals.each do |k,v| columns << k values << v end when Dataset, Array, LiteralString values = vals end when 2 if (v0 = values[0]).is_a?(Array) && ((v1 = values[1]).is_a?(Array) || v1.is_a?(Dataset) || v1.is_a?(LiteralString)) columns, values = v0, v1 raise(Error, "Different number of values and columns given to insert_sql") if values.is_a?(Array) and columns.length != values.length end end if values.is_a?(Array) && values.empty? && !insert_supports_empty_values? columns, values = insert_empty_columns_values end clone(:columns=>columns, :values=>values).send(:_insert_sql) end # Append a literal representation of a value to the given SQL string. # # If an unsupported object is given, an +Error+ is raised. def literal_append(sql, v) case v when Symbol if skip_symbol_cache? literal_symbol_append(sql, v) else unless l = db.literal_symbol(v) l = String.new literal_symbol_append(l, v) db.literal_symbol_set(v, l) end sql << l end when String case v when LiteralString sql << v when SQL::Blob literal_blob_append(sql, v) else literal_string_append(sql, v) end when Integer sql << literal_integer(v) when Hash literal_hash_append(sql, v) when SQL::Expression literal_expression_append(sql, v) when Float sql << literal_float(v) when BigDecimal sql << literal_big_decimal(v) when NilClass sql << literal_nil when TrueClass sql << literal_true when FalseClass sql << literal_false when Array literal_array_append(sql, v) when Time v.is_a?(SQLTime) ? literal_sqltime_append(sql, v) : literal_time_append(sql, v) when DateTime literal_datetime_append(sql, v) when Date sql << literal_date(v) when Dataset literal_dataset_append(sql, v) else literal_other_append(sql, v) end end # Returns an array of insert statements for inserting multiple records. # This method is used by +multi_insert+ to format insert statements and # expects a keys array and and an array of value arrays. # # This method should be overridden by descendants if the support # inserting multiple records in a single SQL statement. def multi_insert_sql(columns, values) case multi_insert_sql_strategy when :values sql = LiteralString.new('VALUES ') expression_list_append(sql, values.map{|r| Array(r)}) [insert_sql(columns, sql)] when :union c = false sql = LiteralString.new u = ' UNION ALL SELECT ' f = empty_from_sql values.each do |v| if c sql << u else sql << 'SELECT ' c = true end expression_list_append(sql, v) sql << f if f end [insert_sql(columns, sql)] else values.map{|r| insert_sql(columns, r)} end end # Same as +select_sql+, not aliased directly to make subclassing simpler. def sql select_sql end # Returns a TRUNCATE SQL query string. See +truncate+ # # DB[:items].truncate_sql # => 'TRUNCATE items' def truncate_sql if opts[:sql] static_sql(opts[:sql]) else check_truncation_allowed! check_not_limited!(:truncate) raise(InvalidOperation, "Can't truncate filtered datasets") if opts[:where] || opts[:having] t = String.new source_list_append(t, opts[:from]) _truncate_sql(t) end end # Formats an UPDATE statement using the given values. See +update+. # # DB[:items].update_sql(:price => 100, :category => 'software') # # => "UPDATE items SET price = 100, category = 'software' # # Raises an +Error+ if the dataset is grouped or includes more # than one table. def update_sql(values = OPTS) return static_sql(opts[:sql]) if opts[:sql] check_modification_allowed! check_not_limited!(:update) case values when LiteralString # nothing when String Sequel::Deprecation.deprecate("Calling Sequel::Dataset#update/update_sql with a plain string", "Use Sequel.lit(#{values.inspect}) to create a literal string and pass that to update/update_sql, or use the auto_literal_strings extension") # raise Error, "plain string passed to Dataset#update" # SEQUEL5 end clone(:values=>values).send(:_update_sql) end # --------------------- # :section: 9 - Internal Methods relating to SQL Creation # These methods, while public, are not designed to be used directly by the end user. # --------------------- # Given a type (e.g. select) and an array of clauses, # return an array of methods to call to build the SQL string. def self.clause_methods(type, clauses) clauses.map{|clause| :"#{type}_#{clause}_sql"}.freeze end # Define a dataset literalization method for the given type in the given module, # using the given clauses. # # Arguments: # mod :: Module in which to define method # type :: Type of SQL literalization method to create, either :select, :insert, :update, or :delete # clauses :: array of clauses that make up the SQL query for the type. This can either be a single # array of symbols/strings, or it can be an array of pairs, with the first element in # each pair being an if/elsif/else code fragment, and the second element in each pair # being an array of symbol/strings for the appropriate branch. def self.def_sql_method(mod, type, clauses) priv = type == :update || type == :insert cacheable = type == :select || type == :delete lines = [] lines << 'private' if priv lines << "def #{'_' if priv}#{type}_sql" lines << 'if sql = opts[:sql]; return static_sql(sql) end' unless priv lines << "if sql = cache_get(:_#{type}_sql); return sql end" if cacheable lines << 'check_modification_allowed!' << 'check_not_limited!(:delete)' if type == :delete lines << 'sql = @opts[:append_sql] || sql_string_origin' if clauses.all?{|c| c.is_a?(Array)} clauses.each do |i, cs| lines << i lines.concat(clause_methods(type, cs).map{|x| "#{x}(sql)"}) end lines << 'end' else lines.concat(clause_methods(type, clauses).map{|x| "#{x}(sql)"}) end lines << "cache_set(:_#{type}_sql, sql) if cache_sql?" if cacheable lines << 'sql' lines << 'end' mod.class_eval lines.join("\n"), __FILE__, __LINE__ end def_sql_method(self, :delete, %w'delete from where') def_sql_method(self, :insert, %w'insert into columns values') def_sql_method(self, :select, %w'with select distinct columns from join where group having compounds order limit lock') def_sql_method(self, :update, %w'update table set where') WILDCARD = LiteralString.new('*').freeze COUNT_OF_ALL_AS_COUNT = SQL::Function.new(:count, WILDCARD).as(:count) DEFAULT = LiteralString.new('DEFAULT').freeze EXISTS = ['EXISTS '.freeze].freeze BITWISE_METHOD_MAP = {:& =>:BITAND, :| => :BITOR, :^ => :BITXOR}#.freeze # SEQUEL5 COUNT_FROM_SELF_OPTS = [:distinct, :group, :sql, :limit, :offset, :compounds]#.freeze # SEQUEL5 IS_LITERALS = {nil=>'NULL'.freeze, true=>'TRUE'.freeze, false=>'FALSE'.freeze}.freeze QUALIFY_KEYS = [:select, :where, :having, :order, :group]#.freeze # SEQUEL5 IS_OPERATORS = ::Sequel::SQL::ComplexExpression::IS_OPERATORS LIKE_OPERATORS = ::Sequel::SQL::ComplexExpression::LIKE_OPERATORS N_ARITY_OPERATORS = ::Sequel::SQL::ComplexExpression::N_ARITY_OPERATORS TWO_ARITY_OPERATORS = ::Sequel::SQL::ComplexExpression::TWO_ARITY_OPERATORS REGEXP_OPERATORS = ::Sequel::SQL::ComplexExpression::REGEXP_OPERATORS EMULATED_FUNCTION_MAP = {} Sequel::Deprecation.deprecate_constant(self, :EMULATED_FUNCTION_MAP) ALL = ' ALL'.freeze Sequel::Deprecation.deprecate_constant(self, :ALL) AND_SEPARATOR = " AND ".freeze Sequel::Deprecation.deprecate_constant(self, :AND_SEPARATOR) APOS = "'".freeze Sequel::Deprecation.deprecate_constant(self, :APOS) APOS_RE = /'/.freeze Sequel::Deprecation.deprecate_constant(self, :APOS_RE) ARRAY_EMPTY = '(NULL)'.freeze Sequel::Deprecation.deprecate_constant(self, :ARRAY_EMPTY) AS = ' AS '.freeze Sequel::Deprecation.deprecate_constant(self, :AS) ASC = ' ASC'.freeze Sequel::Deprecation.deprecate_constant(self, :ASC) BACKSLASH = "\\".freeze Sequel::Deprecation.deprecate_constant(self, :BACKSLASH) BITCOMP_CLOSE = ") - 1)".freeze Sequel::Deprecation.deprecate_constant(self, :BITCOMP_CLOSE) BITCOMP_OPEN = "((0 - ".freeze Sequel::Deprecation.deprecate_constant(self, :BITCOMP_OPEN) BOOL_FALSE = "'f'".freeze Sequel::Deprecation.deprecate_constant(self, :BOOL_FALSE) BOOL_TRUE = "'t'".freeze Sequel::Deprecation.deprecate_constant(self, :BOOL_TRUE) BRACKET_CLOSE = ']'.freeze Sequel::Deprecation.deprecate_constant(self, :BRACKET_CLOSE) BRACKET_OPEN = '['.freeze Sequel::Deprecation.deprecate_constant(self, :BRACKET_OPEN) CASE_ELSE = " ELSE ".freeze Sequel::Deprecation.deprecate_constant(self, :CASE_ELSE) CASE_END = " END)".freeze Sequel::Deprecation.deprecate_constant(self, :CASE_END) CASE_OPEN = '(CASE'.freeze Sequel::Deprecation.deprecate_constant(self, :CASE_OPEN) CASE_THEN = " THEN ".freeze Sequel::Deprecation.deprecate_constant(self, :CASE_THEN) CASE_WHEN = " WHEN ".freeze Sequel::Deprecation.deprecate_constant(self, :CASE_WHEN) CAST_OPEN = 'CAST('.freeze Sequel::Deprecation.deprecate_constant(self, :CAST_OPEN) COLON = ':'.freeze Sequel::Deprecation.deprecate_constant(self, :COLON) COLUMN_REF_RE1 = /\A((?:(?!__).)+)__((?:(?!___).)+)___(.+)\z/.freeze Sequel::Deprecation.deprecate_constant(self, :COLUMN_REF_RE1) COLUMN_REF_RE2 = /\A((?:(?!___).)+)___(.+)\z/.freeze Sequel::Deprecation.deprecate_constant(self, :COLUMN_REF_RE2) COLUMN_REF_RE3 = /\A((?:(?!__).)+)__(.+)\z/.freeze Sequel::Deprecation.deprecate_constant(self, :COLUMN_REF_RE3) COMMA = ', '.freeze Sequel::Deprecation.deprecate_constant(self, :COMMA) COMMA_SEPARATOR = ', '.freeze Sequel::Deprecation.deprecate_constant(self, :COMMA_SEPARATOR) CONDITION_FALSE = '(1 = 0)'.freeze Sequel::Deprecation.deprecate_constant(self, :CONDITION_FALSE) CONDITION_TRUE = '(1 = 1)'.freeze Sequel::Deprecation.deprecate_constant(self, :CONDITION_TRUE) DATASET_ALIAS_BASE_NAME = 't'.freeze Sequel::Deprecation.deprecate_constant(self, :DATASET_ALIAS_BASE_NAME) DATETIME_SECFRACTION_ARG = RUBY_VERSION >= '1.9.0' ? 1000000 : 86400000000 Sequel::Deprecation.deprecate_constant(self, :DATETIME_SECFRACTION_ARG) DEFAULT_VALUES = " DEFAULT VALUES".freeze Sequel::Deprecation.deprecate_constant(self, :DEFAULT_VALUES) DELETE = 'DELETE'.freeze Sequel::Deprecation.deprecate_constant(self, :DELETE) DESC = ' DESC'.freeze Sequel::Deprecation.deprecate_constant(self, :DESC) DISTINCT = " DISTINCT".freeze Sequel::Deprecation.deprecate_constant(self, :DISTINCT) DOT = '.'.freeze Sequel::Deprecation.deprecate_constant(self, :DOT) DOUBLE_APOS = "''".freeze Sequel::Deprecation.deprecate_constant(self, :DOUBLE_APOS) DOUBLE_QUOTE = '""'.freeze Sequel::Deprecation.deprecate_constant(self, :DOUBLE_QUOTE) EQUAL = ' = '.freeze Sequel::Deprecation.deprecate_constant(self, :EQUAL) EMPTY_PARENS = '()'.freeze Sequel::Deprecation.deprecate_constant(self, :EMPTY_PARENS) ESCAPE = " ESCAPE ".freeze Sequel::Deprecation.deprecate_constant(self, :ESCAPE) EXTRACT = 'extract('.freeze Sequel::Deprecation.deprecate_constant(self, :EXTRACT) FILTER = " FILTER (WHERE ".freeze Sequel::Deprecation.deprecate_constant(self, :FILTER) FOR_UPDATE = ' FOR UPDATE'.freeze Sequel::Deprecation.deprecate_constant(self, :FOR_UPDATE) FORMAT_DATE = "'%Y-%m-%d'".freeze Sequel::Deprecation.deprecate_constant(self, :FORMAT_DATE) FORMAT_DATE_STANDARD = "DATE '%Y-%m-%d'".freeze Sequel::Deprecation.deprecate_constant(self, :FORMAT_DATE_STANDARD) FORMAT_OFFSET = "%+03i%02i".freeze Sequel::Deprecation.deprecate_constant(self, :FORMAT_OFFSET) FORMAT_TIMESTAMP_RE = /%[Nz]/.freeze Sequel::Deprecation.deprecate_constant(self, :FORMAT_TIMESTAMP_RE) FORMAT_USEC = '%N'.freeze Sequel::Deprecation.deprecate_constant(self, :FORMAT_USEC) FRAME_ALL = "ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING".freeze Sequel::Deprecation.deprecate_constant(self, :FRAME_ALL) FRAME_ROWS = "ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW".freeze Sequel::Deprecation.deprecate_constant(self, :FRAME_ROWS) FROM = ' FROM '.freeze Sequel::Deprecation.deprecate_constant(self, :FROM) FUNCTION_DISTINCT = "DISTINCT ".freeze Sequel::Deprecation.deprecate_constant(self, :FUNCTION_DISTINCT) GROUP_BY = " GROUP BY ".freeze Sequel::Deprecation.deprecate_constant(self, :GROUP_BY) HAVING = " HAVING ".freeze Sequel::Deprecation.deprecate_constant(self, :HAVING) INSERT = "INSERT".freeze Sequel::Deprecation.deprecate_constant(self, :INSERT) INTO = " INTO ".freeze Sequel::Deprecation.deprecate_constant(self, :INTO) LATERAL = 'LATERAL '.freeze Sequel::Deprecation.deprecate_constant(self, :LATERAL) LIMIT = " LIMIT ".freeze Sequel::Deprecation.deprecate_constant(self, :LIMIT) NOT_SPACE = 'NOT '.freeze Sequel::Deprecation.deprecate_constant(self, :NOT_SPACE) NULL = "NULL".freeze Sequel::Deprecation.deprecate_constant(self, :NULL) NULLS_FIRST = " NULLS FIRST".freeze Sequel::Deprecation.deprecate_constant(self, :NULLS_FIRST) NULLS_LAST = " NULLS LAST".freeze Sequel::Deprecation.deprecate_constant(self, :NULLS_LAST) OFFSET = " OFFSET ".freeze Sequel::Deprecation.deprecate_constant(self, :OFFSET) ON = ' ON '.freeze Sequel::Deprecation.deprecate_constant(self, :ON) ON_PAREN = " ON (".freeze Sequel::Deprecation.deprecate_constant(self, :ON_PAREN) ORDER_BY = " ORDER BY ".freeze Sequel::Deprecation.deprecate_constant(self, :ORDER_BY) ORDER_BY_NS = "ORDER BY ".freeze Sequel::Deprecation.deprecate_constant(self, :ORDER_BY_NS) OVER = ' OVER '.freeze Sequel::Deprecation.deprecate_constant(self, :OVER) PAREN_CLOSE = ')'.freeze Sequel::Deprecation.deprecate_constant(self, :PAREN_CLOSE) PAREN_OPEN = '('.freeze Sequel::Deprecation.deprecate_constant(self, :PAREN_OPEN) PAREN_SPACE_OPEN = ' ('.freeze Sequel::Deprecation.deprecate_constant(self, :PAREN_SPACE_OPEN) PARTITION_BY = "PARTITION BY ".freeze Sequel::Deprecation.deprecate_constant(self, :PARTITION_BY) QUESTION_MARK = '?'.freeze Sequel::Deprecation.deprecate_constant(self, :QUESTION_MARK) QUESTION_MARK_RE = /\?/.freeze Sequel::Deprecation.deprecate_constant(self, :QUESTION_MARK_RE) QUOTE = '"'.freeze Sequel::Deprecation.deprecate_constant(self, :QUOTE) QUOTE_RE = /"/.freeze Sequel::Deprecation.deprecate_constant(self, :QUOTE_RE) RETURNING = " RETURNING ".freeze Sequel::Deprecation.deprecate_constant(self, :RETURNING) SELECT = 'SELECT'.freeze Sequel::Deprecation.deprecate_constant(self, :SELECT) SET = ' SET '.freeze Sequel::Deprecation.deprecate_constant(self, :SET) SPACE = ' '.freeze Sequel::Deprecation.deprecate_constant(self, :SPACE) SQL_WITH = "WITH ".freeze Sequel::Deprecation.deprecate_constant(self, :SQL_WITH) SPACE_WITH = " WITH ".freeze Sequel::Deprecation.deprecate_constant(self, :SPACE_WITH) TILDE = '~'.freeze Sequel::Deprecation.deprecate_constant(self, :TILDE) TIMESTAMP_FORMAT = "'%Y-%m-%d %H:%M:%S%N%z'".freeze Sequel::Deprecation.deprecate_constant(self, :TIMESTAMP_FORMAT) STANDARD_TIMESTAMP_FORMAT = "TIMESTAMP '%Y-%m-%d %H:%M:%S%N%z'".freeze Sequel::Deprecation.deprecate_constant(self, :STANDARD_TIMESTAMP_FORMAT) UNDERSCORE = '_'.freeze Sequel::Deprecation.deprecate_constant(self, :UNDERSCORE) UPDATE = 'UPDATE'.freeze Sequel::Deprecation.deprecate_constant(self, :UPDATE) USING = ' USING ('.freeze Sequel::Deprecation.deprecate_constant(self, :USING) UNION_ALL_SELECT = ' UNION ALL SELECT '.freeze Sequel::Deprecation.deprecate_constant(self, :UNION_ALL_SELECT) VALUES = " VALUES ".freeze Sequel::Deprecation.deprecate_constant(self, :VALUES) WHERE = " WHERE ".freeze Sequel::Deprecation.deprecate_constant(self, :WHERE) WITH_ORDINALITY = " WITH ORDINALITY".freeze Sequel::Deprecation.deprecate_constant(self, :WITH_ORDINALITY) WITHIN_GROUP = " WITHIN GROUP (ORDER BY ".freeze Sequel::Deprecation.deprecate_constant(self, :WITHIN_GROUP) [:literal, :quote_identifier, :quote_schema_table].each do |meth| class_eval(<<-END, __FILE__, __LINE__ + 1) def #{meth}(*args, &block) s = ''.dup #{meth}_append(s, *args, &block) s end END end # Append literalization of aliased expression to SQL string. def aliased_expression_sql_append(sql, ae) literal_append(sql, ae.expression) as_sql_append(sql, ae.alias, ae.columns) end # Append literalization of array to SQL string. def array_sql_append(sql, a) if a.empty? sql << '(NULL)' else sql << '(' expression_list_append(sql, a) sql << ')' end end # Append literalization of boolean constant to SQL string. def boolean_constant_sql_append(sql, constant) if (constant == true || constant == false) && !supports_where_true? sql << (constant == true ? '(1 = 1)' : '(1 = 0)') else literal_append(sql, constant) end end # Append literalization of case expression to SQL string. def case_expression_sql_append(sql, ce) sql << '(CASE' if ce.expression? sql << ' ' literal_append(sql, ce.expression) end w = " WHEN " t = " THEN " ce.conditions.each do |c,r| sql << w literal_append(sql, c) sql << t literal_append(sql, r) end sql << " ELSE " literal_append(sql, ce.default) sql << " END)" end # Append literalization of cast expression to SQL string. def cast_sql_append(sql, expr, type) sql << 'CAST(' literal_append(sql, expr) sql << ' AS ' << db.cast_type_literal(type).to_s sql << ')' end # Append literalization of column all selection to SQL string. def column_all_sql_append(sql, ca) qualified_identifier_sql_append(sql, ca.table, WILDCARD) end # Append literalization of complex expression to SQL string. def complex_expression_sql_append(sql, op, args) case op when *IS_OPERATORS r = args[1] if r.nil? || supports_is_true? raise(InvalidOperation, 'Invalid argument used for IS operator') unless val = IS_LITERALS[r] sql << '(' literal_append(sql, args[0]) sql << ' ' << op.to_s << ' ' sql << val << ')' elsif op == :IS complex_expression_sql_append(sql, :"=", args) else complex_expression_sql_append(sql, :OR, [SQL::BooleanExpression.new(:"!=", *args), SQL::BooleanExpression.new(:IS, args[0], nil)]) end when :IN, :"NOT IN" cols = args[0] vals = args[1] col_array = true if cols.is_a?(Array) if vals.is_a?(Array) val_array = true empty_val_array = vals == [] end if empty_val_array literal_append(sql, empty_array_value(op, cols)) elsif col_array if !supports_multiple_column_in? if val_array expr = SQL::BooleanExpression.new(:OR, *vals.to_a.map{|vs| SQL::BooleanExpression.from_value_pairs(cols.to_a.zip(vs).map{|c, v| [c, v]})}) literal_append(sql, op == :IN ? expr : ~expr) else old_vals = vals vals = vals.naked if vals.is_a?(Sequel::Dataset) vals = vals.to_a val_cols = old_vals.columns complex_expression_sql_append(sql, op, [cols, vals.map!{|x| x.values_at(*val_cols)}]) end else # If the columns and values are both arrays, use array_sql instead of # literal so that if values is an array of two element arrays, it # will be treated as a value list instead of a condition specifier. sql << '(' literal_append(sql, cols) sql << ' ' << op.to_s << ' ' if val_array array_sql_append(sql, vals) else literal_append(sql, vals) end sql << ')' end else sql << '(' literal_append(sql, cols) sql << ' ' << op.to_s << ' ' literal_append(sql, vals) sql << ')' end when :LIKE, :'NOT LIKE' sql << '(' literal_append(sql, args[0]) sql << ' ' << op.to_s << ' ' literal_append(sql, args[1]) sql << " ESCAPE " literal_append(sql, "\\") sql << ')' when :ILIKE, :'NOT ILIKE' complex_expression_sql_append(sql, (op == :ILIKE ? :LIKE : :"NOT LIKE"), args.map{|v| Sequel.function(:UPPER, v)}) when :** function_sql_append(sql, Sequel.function(:power, *args)) when *TWO_ARITY_OPERATORS if REGEXP_OPERATORS.include?(op) && !supports_regexp? raise InvalidOperation, "Pattern matching via regular expressions is not supported on #{db.database_type}" end sql << '(' literal_append(sql, args[0]) sql << ' ' << op.to_s << ' ' literal_append(sql, args[1]) sql << ')' when *N_ARITY_OPERATORS sql << '(' c = false op_str = " #{op} " args.each do |a| sql << op_str if c literal_append(sql, a) c ||= true end sql << ')' when :NOT sql << 'NOT ' literal_append(sql, args[0]) when :NOOP literal_append(sql, args[0]) when :'B~' sql << '~' literal_append(sql, args[0]) when :extract sql << 'extract(' << args[0].to_s << ' FROM ' literal_append(sql, args[1]) sql << ')' else raise(InvalidOperation, "invalid operator #{op}") end end # Append literalization of constant to SQL string. def constant_sql_append(sql, constant) sql << constant.to_s end # Append literalization of delayed evaluation to SQL string, # causing the delayed evaluation proc to be evaluated. def delayed_evaluation_sql_append(sql, delay) # Delayed evaluations are used specifically so the SQL # can differ in subsequent calls, so we definitely don't # want to cache the sql in this case. disable_sql_caching! if recorder = @opts[:placeholder_literalizer] recorder.use(sql, lambda{delay.call(self)}, nil) else literal_append(sql, delay.call(self)) end end # Append literalization of function call to SQL string. def function_sql_append(sql, f) name = f.name opts = f.opts if opts[:emulate] if emulate_function?(name) emulate_function_sql_append(sql, f) return end name = native_function_name(name) end sql << 'LATERAL ' if opts[:lateral] case name when SQL::Identifier if supports_quoted_function_names? && opts[:quoted] literal_append(sql, name) else sql << name.value.to_s end when SQL::QualifiedIdentifier if supports_quoted_function_names? && opts[:quoted] != false literal_append(sql, name) else sql << split_qualifiers(name).join('.') end else if supports_quoted_function_names? && opts[:quoted] quote_identifier_append(sql, name) else sql << name.to_s end end sql << '(' if opts[:*] sql << '*' else sql << "DISTINCT " if opts[:distinct] expression_list_append(sql, f.args) if order = opts[:order] sql << " ORDER BY " expression_list_append(sql, order) end end sql << ')' if group = opts[:within_group] sql << " WITHIN GROUP (ORDER BY " expression_list_append(sql, group) sql << ')' end if filter = opts[:filter] sql << " FILTER (WHERE " literal_append(sql, filter_expr(filter, &opts[:filter_block])) sql << ')' end if window = opts[:over] sql << ' OVER ' window_sql_append(sql, window.opts) end if opts[:with_ordinality] sql << " WITH ORDINALITY" end end # Append literalization of JOIN clause without ON or USING to SQL string. def join_clause_sql_append(sql, jc) table = jc.table table_alias = jc.table_alias table_alias = nil if table == table_alias && !jc.column_aliases sql << ' ' << join_type_sql(jc.join_type) << ' ' identifier_append(sql, table) as_sql_append(sql, table_alias, jc.column_aliases) if table_alias end # Append literalization of JOIN ON clause to SQL string. def join_on_clause_sql_append(sql, jc) join_clause_sql_append(sql, jc) sql << ' ON ' literal_append(sql, filter_expr(jc.on)) end # Append literalization of JOIN USING clause to SQL string. def join_using_clause_sql_append(sql, jc) join_clause_sql_append(sql, jc) sql << ' USING (' column_list_append(sql, jc.using) sql << ')' end # Append literalization of negative boolean constant to SQL string. def negative_boolean_constant_sql_append(sql, constant) sql << 'NOT ' boolean_constant_sql_append(sql, constant) end # Append literalization of ordered expression to SQL string. def ordered_expression_sql_append(sql, oe) literal_append(sql, oe.expression) sql << (oe.descending ? ' DESC' : ' ASC') case oe.nulls when :first sql << " NULLS FIRST" when :last sql << " NULLS LAST" end end # Append literalization of placeholder literal string to SQL string. def placeholder_literal_string_sql_append(sql, pls) args = pls.args str = pls.str sql << '(' if pls.parens if args.is_a?(Hash) if args.empty? sql << str else re = /:(#{args.keys.map{|k| Regexp.escape(k.to_s)}.join('|')})\b/ loop do previous, q, str = str.partition(re) sql << previous literal_append(sql, args[($1||q[1..-1].to_s).to_sym]) unless q.empty? break if str.empty? end end elsif str.is_a?(Array) len = args.length str.each_with_index do |s, i| sql << s literal_append(sql, args[i]) unless i == len end unless str.length == args.length || str.length == args.length + 1 raise Error, "Mismatched number of placeholders (#{str.length}) and placeholder arguments (#{args.length}) when using placeholder array" end else i = -1 match_len = args.length - 1 loop do previous, q, str = str.partition('?') sql << previous literal_append(sql, args.at(i+=1)) unless q.empty? if str.empty? unless i == match_len raise Error, "Mismatched number of placeholders (#{i+1}) and placeholder arguments (#{args.length}) when using placeholder array" end break end end end sql << ')' if pls.parens end # Append literalization of qualified identifier to SQL string. # If 3 arguments are given, the 2nd should be the table/qualifier and the third should be # column/qualified. If 2 arguments are given, the 2nd should be an SQL::QualifiedIdentifier. def qualified_identifier_sql_append(sql, table, column=(c = table.column; table = table.table; c)) identifier_append(sql, table) sql << '.' identifier_append(sql, column) end # Append literalization of unqualified identifier to SQL string. # Adds quoting to identifiers (columns and tables). If identifiers are not # being quoted, returns name as a string. If identifiers are being quoted # quote the name with quoted_identifier. def quote_identifier_append(sql, name) if name.is_a?(LiteralString) sql << name else name = name.value if name.is_a?(SQL::Identifier) name = input_identifier(name) if quote_identifiers? quoted_identifier_append(sql, name) else sql << name end end end # Append literalization of identifier or unqualified identifier to SQL string. def quote_schema_table_append(sql, table) schema, table = schema_and_table(table) if schema quote_identifier_append(sql, schema) sql << '.' end quote_identifier_append(sql, table) end # Append literalization of quoted identifier to SQL string. # This method quotes the given name with the SQL standard double quote. # should be overridden by subclasses to provide quoting not matching the # SQL standard, such as backtick (used by MySQL and SQLite). def quoted_identifier_append(sql, name) sql << '"' << name.to_s.gsub('"', '""') << '"' end # Split the schema information from the table, returning two strings, # one for the schema and one for the table. The returned schema may # be nil, but the table will always have a string value. # # Note that this function does not handle tables with more than one # level of qualification (e.g. database.schema.table on Microsoft # SQL Server). def schema_and_table(table_name, sch=nil) sch = sch.to_s if sch case table_name when Symbol s, t, _ = split_symbol(table_name) [s||sch, t] when SQL::QualifiedIdentifier [table_name.table.to_s, table_name.column.to_s] when SQL::Identifier [sch, table_name.value.to_s] when String [sch, table_name] else raise Error, 'table_name should be a Symbol, SQL::QualifiedIdentifier, SQL::Identifier, or String' end end # Splits table_name into an array of strings. # # ds.split_qualifiers(:s) # ['s'] # ds.split_qualifiers(:t__s) # ['t', 's'] # ds.split_qualifiers(Sequel[:d][:t__s]) # ['d', 't', 's'] # ds.split_qualifiers(Sequel[:h__d][:t__s]) # ['h', 'd', 't', 's'] def split_qualifiers(table_name, *args) case table_name when SQL::QualifiedIdentifier split_qualifiers(table_name.table, nil) + split_qualifiers(table_name.column, nil) else sch, table = schema_and_table(table_name, *args) sch ? [sch, table] : [table] end end # Append literalization of subscripts (SQL array accesses) to SQL string. def subscript_sql_append(sql, s) literal_append(sql, s.f) sql << '[' if s.sub.length == 1 && (range = s.sub.first).is_a?(Range) literal_append(sql, range.begin) sql << ':' e = range.end e -= 1 if range.exclude_end? && e.is_a?(Integer) literal_append(sql, e) else expression_list_append(sql, s.sub) end sql << ']' end # Append literalization of windows (for window functions) to SQL string. def window_sql_append(sql, opts) raise(Error, 'This dataset does not support window functions') unless supports_window_functions? sql << '(' window, part, order, frame = opts.values_at(:window, :partition, :order, :frame) space = false space_s = ' ' if window literal_append(sql, window) space = true end if part sql << space_s if space sql << "PARTITION BY " expression_list_append(sql, Array(part)) space = true end if order sql << space_s if space sql << "ORDER BY " expression_list_append(sql, Array(order)) space = true end case frame when nil # nothing when :all sql << space_s if space sql << "ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING" when :rows sql << space_s if space sql << "ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW" when String sql << space_s if space sql << frame else raise Error, "invalid window frame clause, should be :all, :rows, a string, or nil" end sql << ')' end protected # Return a from_self dataset if an order or limit is specified, so it works as expected # with UNION, EXCEPT, and INTERSECT clauses. def compound_from_self (@opts[:sql] || @opts[:limit] || @opts[:order] || @opts[:offset]) ? from_self : self end private # Formats the truncate statement. Assumes the table given has already been # literalized. def _truncate_sql(table) "TRUNCATE TABLE #{table}" end # Returns an appropriate symbol for the alias represented by s. def alias_alias_symbol(s) case s when Symbol s when String s.to_sym when SQL::Identifier s.value.to_s.to_sym else raise Error, "Invalid alias for alias_alias_symbol: #{s.inspect}" end end # Returns an appropriate alias symbol for the given object, which can be # a Symbol, String, SQL::Identifier, SQL::QualifiedIdentifier, or # SQL::AliasedExpression. def alias_symbol(sym) case sym when Symbol s, t, a = split_symbol(sym) a || s ? (a || t).to_sym : sym when String sym.to_sym when SQL::Identifier sym.value.to_s.to_sym when SQL::QualifiedIdentifier alias_symbol(sym.column) when SQL::AliasedExpression alias_alias_symbol(sym.alias) else raise Error, "Invalid alias for alias_symbol: #{sym.inspect}" end end # Clone of this dataset usable in aggregate operations. Does # a from_self if dataset contains any parameters that would # affect normal aggregation, or just removes an existing # order if not. def aggregate_dataset options_overlap(COUNT_FROM_SELF_OPTS) ? from_self : unordered end # Append aliasing expression to SQL string. def as_sql_append(sql, aliaz, column_aliases=nil) sql << ' AS ' quote_identifier_append(sql, aliaz) if column_aliases raise Error, "#{db.database_type} does not support derived column lists" unless supports_derived_column_lists? sql << '(' identifier_list_append(sql, column_aliases) sql << ')' end end # Only allow caching the select SQL if the dataset is frozen and hasn't # specifically been marked as not allowing SQL caching. def cache_sql? frozen? && !@opts[:no_cache_sql] && !cache_get(:_no_cache_sql) end # Raise an InvalidOperation exception if deletion is not allowed # for this dataset def check_modification_allowed! raise(InvalidOperation, "Grouped datasets cannot be modified") if opts[:group] raise(InvalidOperation, "Joined datasets cannot be modified") if !supports_modifying_joins? && joined_dataset? end # Emit deprecation warning if the dataset uses limits or offsets. def check_not_limited!(type) return if @opts[:skip_limit_check] && type != :truncate # SEQUEL5 #raise InvalidOperation, "Dataset##{type} not supported on datasets with limits or offsets" if opts[:limit] || opts[:offset] Sequel::Deprecation.deprecate("Dataset##{type} on datasets with limits or offsets", "Call unlimited to remove the limit #{'or skip_limit_check to ignore the limit ' unless type == :truncate}before calling #{type}") if @opts[:limit] || @opts[:offset] end # Alias of check_modification_allowed! def check_truncation_allowed! check_modification_allowed! end # Append column list to SQL string. # Converts an array of column names into a comma seperated string of # column names. If the array is empty, a wildcard (*) is returned. def column_list_append(sql, columns) if (columns.nil? || columns.empty?) sql << '*' else expression_list_append(sql, columns) end end # Yield each pair of arguments to the block, which should # return an object representing the SQL expression for those # two arguments. For more than two arguments, the first # argument to the block will be result of the previous block call. def complex_expression_arg_pairs(args) case args.length when 1 args[0] when 2 yield args[0], args[1] else args.inject{|m, a| yield(m, a)} end end # Append the literalization of the args using complex_expression_arg_pairs # to the given SQL string, used when database operator/function is 2-ary # where Sequel expression is N-ary. def complex_expression_arg_pairs_append(sql, args, &block) literal_append(sql, complex_expression_arg_pairs(args, &block)) end # Append literalization of complex expression to SQL string, for # operators unsupported by some databases. Used by adapters for databases # that don't support the operators natively. def complex_expression_emulate_append(sql, op, args) case op when :% complex_expression_arg_pairs_append(sql, args){|a, b| Sequel.function(:MOD, a, b)} when :>> complex_expression_arg_pairs_append(sql, args){|a, b| Sequel./(a, Sequel.function(:power, 2, b))} when :<< complex_expression_arg_pairs_append(sql, args){|a, b| Sequel.*(a, Sequel.function(:power, 2, b))} when :&, :|, :^ f = BITWISE_METHOD_MAP[op] complex_expression_arg_pairs_append(sql, args){|a, b| Sequel.function(f, a, b)} when :'B~' sql << "((0 - " literal_append(sql, args[0]) sql << ") - 1)" end end # Append literalization of dataset used in UNION/INTERSECT/EXCEPT clause to SQL string. def compound_dataset_sql_append(sql, ds) subselect_sql_append(sql, ds) end # The alias to use for datasets, takes a number to make sure the name is unique. def dataset_alias(number) :"t#{number}" end # The strftime format to use when literalizing the time. def default_timestamp_format requires_sql_standard_datetimes? ? "TIMESTAMP '%Y-%m-%d %H:%M:%S%N%z'" : "'%Y-%m-%d %H:%M:%S%N%z'" end def delete_delete_sql(sql) sql << 'DELETE' end def delete_from_sql(sql) if f = @opts[:from] sql << ' FROM ' source_list_append(sql, f) end end # Disable caching of SQL for the current dataset def disable_sql_caching! cache_set(:_no_cache_sql, true) end # An SQL FROM clause to use in SELECT statements where the dataset has # no from tables. def empty_from_sql nil end # Whether to emulate the function with the given name. This should only be true # if the emulation goes beyond choosing a function with a different name. def emulate_function?(name) false end # Append literalization of array of expressions to SQL string. def expression_list_append(sql, columns) c = false co = ', ' columns.each do |col| sql << co if c literal_append(sql, col) c ||= true end end # Append literalization of array of grouping elements to SQL string. def grouping_element_list_append(sql, columns) c = false co = ', ' columns.each do |col| sql << co if c if col.is_a?(Array) && col.empty? sql << '()' else literal_append(sql, Array(col)) end c ||= true end end # An expression for how to handle an empty array lookup. def empty_array_value(op, cols) {1 => ((op == :IN) ? 0 : 1)} end # Format the timestamp based on the default_timestamp_format, with a couple # of modifiers. First, allow %N to be used for fractions seconds (if the # database supports them), and override %z to always use a numeric offset # of hours and minutes. def format_timestamp(v) v2 = db.from_application_timestamp(v) fmt = default_timestamp_format.gsub(/%[Nz]/) do |m| if m == '%N' format_timestamp_usec(v.is_a?(DateTime) ? v.sec_fraction*(RUBY_VERSION >= '1.9.0' ? 1000000 : 86400000000) : v.usec) if supports_timestamp_usecs? else if supports_timestamp_timezones? # Would like to just use %z format, but it doesn't appear to work on Windows # Instead, the offset fragment is constructed manually minutes = (v2.is_a?(DateTime) ? v2.offset * 1440 : v2.utc_offset/60).to_i format_timestamp_offset(*minutes.divmod(60)) end end end v2.strftime(fmt) end # Return the SQL timestamp fragment to use for the timezone offset. def format_timestamp_offset(hour, minute) sprintf("%+03i%02i", hour, minute) end # Return the SQL timestamp fragment to use for the fractional time part. # Should start with the decimal point. Uses 6 decimal places by default. def format_timestamp_usec(usec, ts=timestamp_precision) unless ts == 6 usec = usec/(10 ** (6 - ts)) end sprintf(".%0#{ts}d", usec) end # Append literalization of identifier to SQL string, considering regular strings # as SQL identifiers instead of SQL strings. def identifier_append(sql, v) if v.is_a?(String) case v when LiteralString sql << v when SQL::Blob literal_append(sql, v) else quote_identifier_append(sql, v) end else literal_append(sql, v) end end # Append literalization of array of identifiers to SQL string. def identifier_list_append(sql, args) c = false comma = ', ' args.each do |a| sql << comma if c identifier_append(sql, a) c ||= true end end # Upcase identifiers by default when inputting them into the database. def input_identifier(v) v.to_s.upcase end def insert_into_sql(sql) sql << " INTO " if (f = @opts[:from]) && f.length == 1 identifier_append(sql, unaliased_identifier(f.first)) else source_list_append(sql, f) end end def insert_columns_sql(sql) columns = opts[:columns] if columns && !columns.empty? sql << ' (' identifier_list_append(sql, columns) sql << ')' end end # The columns and values to use for an empty insert if the database doesn't support # INSERT with DEFAULT VALUES. def insert_empty_columns_values [[columns.last], [DEFAULT]] end def insert_insert_sql(sql) sql << "INSERT" end def insert_values_sql(sql) case values = opts[:values] when Array if values.empty? sql << " DEFAULT VALUES" else sql << " VALUES " literal_append(sql, values) end when Dataset sql << ' ' subselect_sql_append(sql, values) when LiteralString sql << ' ' << values else raise Error, "Unsupported INSERT values type, should be an Array or Dataset: #{values.inspect}" end end def insert_returning_sql(sql) if opts.has_key?(:returning) sql << " RETURNING " column_list_append(sql, Array(opts[:returning])) end end alias delete_returning_sql insert_returning_sql alias update_returning_sql insert_returning_sql # SQL fragment specifying a JOIN type, converts underscores to # spaces and upcases. def join_type_sql(join_type) "#{join_type.to_s.gsub('_', ' ').upcase} JOIN" end # Append a literalization of the array to SQL string. # Treats as an expression if an array of all two pairs, or as a SQL array otherwise. def literal_array_append(sql, v) if Sequel.condition_specifier?(v) literal_expression_append(sql, SQL::BooleanExpression.from_value_pairs(v)) else array_sql_append(sql, v) end end # SQL fragment for BigDecimal def literal_big_decimal(v) d = v.to_s("F") v.nan? || v.infinite? ? "'#{d}'" : d end # Append literalization of SQL::Blob to SQL string. def literal_blob_append(sql, v) literal_string_append(sql, v) end # Append literalization of dataset to SQL string. Does a subselect inside parantheses. def literal_dataset_append(sql, v) sql << 'LATERAL ' if v.opts[:lateral] sql << '(' subselect_sql_append(sql, v) sql << ')' end # SQL fragment for Date, using the ISO8601 format. def literal_date(v) if requires_sql_standard_datetimes? v.strftime("DATE '%Y-%m-%d'") else v.strftime("'%Y-%m-%d'") end end # SQL fragment for DateTime def literal_datetime(v) format_timestamp(v) end # Append literalization of DateTime to SQL string. def literal_datetime_append(sql, v) sql << literal_datetime(v) end # Append literalization of SQL::Expression to SQL string. def literal_expression_append(sql, v) v.to_s_append(self, sql) end # SQL fragment for false def literal_false "'f'" end # SQL fragment for Float def literal_float(v) v.to_s end # Append literalization of Hash to SQL string, treating hash as a boolean expression. def literal_hash_append(sql, v) literal_expression_append(sql, SQL::BooleanExpression.from_value_pairs(v)) end # SQL fragment for Integer def literal_integer(v) v.to_s end # SQL fragment for nil def literal_nil "NULL" end # Append a literalization of the object to the given SQL string. # Calls +sql_literal_append+ if object responds to it, otherwise # calls +sql_literal+ if object responds to it, otherwise raises an error. # If a database specific type is allowed, this should be overriden in a subclass. def literal_other_append(sql, v) # We can't be sure if v will always literalize to the same SQL, so # don't cache SQL for a dataset that uses this. disable_sql_caching! if v.respond_to?(:sql_literal_append) v.sql_literal_append(self, sql) elsif v.respond_to?(:sql_literal) sql << v.sql_literal(self) else raise Error, "can't express #{v.inspect} as a SQL literal" end end # SQL fragment for Sequel::SQLTime, containing just the time part def literal_sqltime(v) v.strftime("'%H:%M:%S#{format_timestamp_usec(v.usec, sqltime_precision) if supports_timestamp_usecs?}'") end # Append literalization of Sequel::SQLTime to SQL string. def literal_sqltime_append(sql, v) sql << literal_sqltime(v) end # Append literalization of string to SQL string. def literal_string_append(sql, v) sql << "'" << v.gsub(/'/, "''") << "'" end # Append literalization of symbol to SQL string. def literal_symbol_append(sql, v) c_table, column, c_alias = split_symbol(v) if c_table quote_identifier_append(sql, c_table) sql << '.' end quote_identifier_append(sql, column) as_sql_append(sql, c_alias) if c_alias end # SQL fragment for Time def literal_time(v) format_timestamp(v) end # Append literalization of Time to SQL string. def literal_time_append(sql, v) sql << literal_time(v) end # SQL fragment for true def literal_true "'t'" end # What strategy to use for import/multi_insert. While SQL-92 defaults # to allowing multiple rows in a VALUES clause, there are enough databases # that don't allow that that it can't be the default. Use separate queries # by default, which works everywhere. def multi_insert_sql_strategy :separate end # Get the native function name given the emulated function name. def native_function_name(emulated_function) emulated_function end # Returns a qualified column name (including a table name) if the column # name isn't already qualified. def qualified_column_name(column, table) if column.is_a?(Symbol) c_table, column, _ = split_symbol(column) unless c_table case table when Symbol schema, table, t_alias = split_symbol(table) t_alias ||= Sequel::SQL::QualifiedIdentifier.new(schema, table) if schema when Sequel::SQL::AliasedExpression t_alias = table.alias end c_table = t_alias || table end ::Sequel::SQL::QualifiedIdentifier.new(c_table, column) else column end end # Qualify the given expression e to the given table. def qualified_expression(e, table) Qualifier.new(table).transform(e) end def select_columns_sql(sql) sql << ' ' column_list_append(sql, @opts[:select]) end def select_distinct_sql(sql) if distinct = @opts[:distinct] sql << " DISTINCT" unless distinct.empty? sql << " ON (" expression_list_append(sql, distinct) sql << ')' end end end # Modify the sql to add a dataset to the via an EXCEPT, INTERSECT, or UNION clause. # This uses a subselect for the compound datasets used, because using parantheses doesn't # work on all databases. I consider this an ugly hack, but can't I think of a better default. def select_compounds_sql(sql) return unless c = @opts[:compounds] c.each do |type, dataset, all| sql << ' ' << type.to_s.upcase sql << ' ALL' if all sql << ' ' compound_dataset_sql_append(sql, dataset) end end def select_from_sql(sql) if f = @opts[:from] sql << ' FROM ' source_list_append(sql, f) elsif f = empty_from_sql sql << f end end def select_group_sql(sql) if group = @opts[:group] sql << " GROUP BY " if go = @opts[:group_options] if go == :"grouping sets" sql << go.to_s.upcase << '(' grouping_element_list_append(sql, group) sql << ')' elsif uses_with_rollup? expression_list_append(sql, group) sql << " WITH " << go.to_s.upcase else sql << go.to_s.upcase << '(' expression_list_append(sql, group) sql << ')' end else expression_list_append(sql, group) end end end def select_having_sql(sql) if having = @opts[:having] sql << " HAVING " literal_append(sql, having) end end def select_join_sql(sql) if js = @opts[:join] js.each{|j| literal_append(sql, j)} end end def select_limit_sql(sql) if l = @opts[:limit] sql << " LIMIT " literal_append(sql, l) if o = @opts[:offset] sql << " OFFSET " literal_append(sql, o) end elsif @opts[:offset] select_only_offset_sql(sql) end end def select_lock_sql(sql) case l = @opts[:lock] when :update sql << ' FOR UPDATE' when String sql << ' ' << l end end # Used only if there is an offset and no limit, making it easier to override # in the adapter, as many databases do not support just a plain offset with # no limit. def select_only_offset_sql(sql) sql << " OFFSET " literal_append(sql, @opts[:offset]) end def select_order_sql(sql) if o = @opts[:order] sql << " ORDER BY " expression_list_append(sql, o) end end alias delete_order_sql select_order_sql alias update_order_sql select_order_sql def select_select_sql(sql) sql << 'SELECT' end def select_where_sql(sql) if w = @opts[:where] sql << " WHERE " literal_append(sql, w) end end alias delete_where_sql select_where_sql alias update_where_sql select_where_sql def select_with_sql(sql) return unless supports_cte? ws = opts[:with] return if !ws || ws.empty? sql << select_with_sql_base c = false comma = ', ' ws.each do |w| sql << comma if c quote_identifier_append(sql, w[:name]) if args = w[:args] sql << '(' identifier_list_append(sql, args) sql << ')' end sql << ' AS ' literal_dataset_append(sql, w[:dataset]) c ||= true end sql << ' ' end alias delete_with_sql select_with_sql alias insert_with_sql select_with_sql alias update_with_sql select_with_sql # The base keyword to use for the SQL WITH clause def select_with_sql_base "WITH " end # Whether the symbol cache should be skipped when literalizing the dataset def skip_symbol_cache? @opts[:skip_symbol_cache] end # Append literalization of array of sources/tables to SQL string, raising an Error if there # are no sources. def source_list_append(sql, sources) raise(Error, 'No source specified for query') if sources.nil? || sources == [] identifier_list_append(sql, sources) end # Delegate to Sequel.split_symbol. def split_symbol(sym) Sequel.split_symbol(sym) end # The string that is appended to to create the SQL query, the empty # string by default def sql_string_origin String.new end # The precision to use for SQLTime instances (time column values without dates). # Defaults to timestamp_precision. def sqltime_precision timestamp_precision end # SQL to use if this dataset uses static SQL. Since static SQL # can be a PlaceholderLiteralString in addition to a String, # we literalize nonstrings. If there is an append_sql for this # dataset, append to that SQL instead of returning the value. def static_sql(sql) if append_sql = @opts[:append_sql] if sql.is_a?(String) append_sql << sql else literal_append(append_sql, sql) end else if sql.is_a?(String) sql else literal(sql) end end end # Append literalization of the subselect to SQL String. def subselect_sql_append(sql, ds) ds.clone(:append_sql=>sql).sql end # The number of decimal digits of precision to use in timestamps. def timestamp_precision supports_timestamp_usecs? ? 6 : 0 end def update_table_sql(sql) sql << ' ' source_list_append(sql, @opts[:from]) select_join_sql(sql) if supports_modifying_joins? end def update_set_sql(sql) sql << ' SET ' values = @opts[:values] if values.is_a?(Hash) update_sql_values_hash(sql, values) else sql << values end end def update_sql_values_hash(sql, values) c = false eq = ' = ' values.each do |k, v| sql << ', ' if c if k.is_a?(String) && !k.is_a?(LiteralString) quote_identifier_append(sql, k) else literal_append(sql, k) end sql << eq literal_append(sql, v) c ||= true end end def update_update_sql(sql) sql << 'UPDATE' end end end