sequel_core 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -116,6 +116,18 @@ module Sequel
116
116
  case op[:op]
117
117
  when :add_column
118
118
  "ALTER TABLE #{table} ADD #{column_definition_sql(op)}"
119
+ when :add_index
120
+ index_definition_sql(table, op)
121
+ when :drop_column
122
+ columns_str = (schema_parse_table(table, {}).map {|c| c[0] } - (Array === op[:name] ? op[:name] : [op[:name]])).join(",")
123
+ sql = "BEGIN TRANSACTION;\n"
124
+ sql += "CREATE TEMPORARY TABLE #{table}_backup(#{columns_str});\n"
125
+ sql += "INSERT INTO #{table}_backup SELECT #{columns_str} FROM #{table};\n"
126
+ sql += "DROP TABLE #{table};\n"
127
+ sql += "CREATE TABLE #{table}(#{columns_str});\n"
128
+ sql += "INSERT INTO #{table} SELECT #{columns_str} FROM #{table}_backup;\n"
129
+ sql += "DROP TABLE #{table}_backup;\n"
130
+ sql += "COMMIT;\n"
119
131
  else
120
132
  raise Error, "Unsupported ALTER TABLE operation"
121
133
  end
@@ -139,10 +139,17 @@ class String
139
139
  def to_sql
140
140
  split("\n").to_sql
141
141
  end
142
+
143
+ # Returns a Blob that holds the same data as this string. Blobs provide proper
144
+ # escaping of binary data.
145
+ def to_blob
146
+ ::Sequel::SQL::Blob.new self
147
+ end
142
148
  end
143
149
 
144
150
  class Symbol
145
151
  include Sequel::SQL::QualifyingMethods
152
+ include Sequel::SQL::IdentifierMethods
146
153
  include Sequel::SQL::GenericExpressionMethods
147
154
 
148
155
  # If no argument is given, returns a Sequel::SQL::ColumnAll object specifying all
@@ -48,8 +48,8 @@ module Sequel
48
48
  def initialize(opts = {}, &block)
49
49
  @opts = opts
50
50
 
51
- @quote_identifiers = opts[:quote_identifiers] || @@quote_identifiers
52
- @single_threaded = opts[:single_threaded] || @@single_threaded
51
+ @quote_identifiers = opts.include?(:quote_identifiers) ? opts[:quote_identifiers] : @@quote_identifiers
52
+ @single_threaded = opts.include?(:single_threaded) ? opts[:single_threaded] : @@single_threaded
53
53
  @schemas = nil
54
54
  @pool = (@single_threaded ? SingleThreadedPool : ConnectionPool).new(connection_pool_default_options.merge(opts), &block)
55
55
  @pool.connection_proc = proc {connect} unless block
@@ -133,25 +133,21 @@ module Sequel
133
133
  # Converts a uri to an options hash. These options are then passed
134
134
  # to a newly created database object.
135
135
  def self.uri_to_options(uri)
136
- if uri.is_a?(String)
137
- uri = URI.parse(uri)
138
- end
136
+ uri = URI.parse(uri) if uri.is_a?(String)
139
137
  # special case for sqlite
140
- if uri.scheme == 'sqlite'
141
- {
142
- :user => uri.user,
138
+ opts = if uri.scheme == 'sqlite'
139
+ { :user => uri.user,
143
140
  :password => uri.password,
144
- :database => (uri.host.nil? && uri.path == '/') ? nil : "#{uri.host}#{uri.path}"
145
- }
141
+ :database => (uri.host.nil? && uri.path == '/') ? nil : "#{uri.host}#{uri.path}" }
146
142
  else
147
- {
148
- :user => uri.user,
143
+ { :user => uri.user,
149
144
  :password => uri.password,
150
145
  :host => uri.host,
151
146
  :port => uri.port,
152
- :database => (m = /\/(.*)/.match(uri.path)) && (m[1])
153
- }
147
+ :database => (m = /\/(.*)/.match(uri.path)) && (m[1]) }
154
148
  end
149
+ uri.query.split('&').collect{|s| s.split('=')}.each{|k,v| opts[k.to_sym] = v} unless uri.query.blank?
150
+ opts
155
151
  end
156
152
 
157
153
  ### Private Class Methods ###
@@ -427,6 +423,8 @@ module Sequel
427
423
  # parse that string using the time class.
428
424
  (Time === value ? value.iso8601 : value.to_s).to_sequel_time
429
425
  end
426
+ when :blob
427
+ value.to_blob
430
428
  else
431
429
  value
432
430
  end
@@ -1,4 +1,4 @@
1
- %w'callback convenience pagination query schema sequelizer sql'.each do |f|
1
+ %w'callback convenience pagination query schema sql'.each do |f|
2
2
  require "sequel_core/dataset/#{f}"
3
3
  end
4
4
 
@@ -187,7 +187,7 @@ module Sequel
187
187
  def clone(opts = {})
188
188
  c = super()
189
189
  c.opts = @opts.merge(opts)
190
- c.instance_variable_set(:@columns, nil) if c.options_overlap(COLUMN_CHANGE_OPTS)
190
+ c.instance_variable_set(:@columns, nil) if opts.keys.any?{|o| COLUMN_CHANGE_OPTS.include?(o)}
191
191
  c
192
192
  end
193
193
 
@@ -198,7 +198,10 @@ module Sequel
198
198
  # If the dataset does not have any rows, this will be an empty array.
199
199
  # If you are looking for all columns for a single table, see Schema::SQL#schema.
200
200
  def columns
201
- single_record unless @columns
201
+ return @columns if @columns
202
+ ds = unfiltered.unordered.clone(:distinct => nil)
203
+ ds.single_record
204
+ @columns = ds.instance_variable_get(:@columns)
202
205
  @columns || []
203
206
  end
204
207
 
@@ -6,7 +6,7 @@ module Sequel
6
6
  COLUMN_REF_RE1 = /\A([\w ]+)__([\w ]+)___([\w ]+)\z/.freeze
7
7
  COLUMN_REF_RE2 = /\A([\w ]+)___([\w ]+)\z/.freeze
8
8
  COLUMN_REF_RE3 = /\A([\w ]+)__([\w ]+)\z/.freeze
9
- COUNT_FROM_SELF_OPTS = [:distinct, :group, :sql]
9
+ COUNT_FROM_SELF_OPTS = [:distinct, :group, :sql, :limit]
10
10
  DATE_FORMAT = "DATE '%Y-%m-%d'".freeze
11
11
  N_ARITY_OPERATORS = ::Sequel::SQL::ComplexExpression::N_ARITY_OPERATORS
12
12
  NULL = "NULL".freeze
@@ -134,8 +134,9 @@ module Sequel
134
134
  # * Sequel::SQL::BooleanExpression - an existing condition expression,
135
135
  # probably created using the Sequel blockless filter DSL.
136
136
  #
137
- # filter also takes a block, but use of this is discouraged as it requires
138
- # ParseTree.
137
+ # filter also takes a block, which should return one of the above argument
138
+ # types, and is treated the same way. If both a block and regular argument
139
+ # are provided, they get ANDed together.
139
140
  #
140
141
  # Examples:
141
142
  #
@@ -527,7 +528,7 @@ module Sequel
527
528
  # SQL fragment for the qualifed identifier, specifying
528
529
  # a table and a column (or schema and table).
529
530
  def qualified_identifier_sql(qcr)
530
- "#{quote_identifier(qcr.table)}.#{quote_identifier(qcr.column)}"
531
+ [qcr.table, qcr.column].map{|x| x.is_one_of?(SQL::QualifiedIdentifier, SQL::Identifier) ? literal(x) : quote_identifier(x)}.join('.')
531
532
  end
532
533
 
533
534
  # Adds quoting to identifiers (columns and tables). If identifiers are not
@@ -588,7 +589,7 @@ module Sequel
588
589
  select_columns = columns ? column_list(columns) : WILDCARD
589
590
 
590
591
  if distinct = opts[:distinct]
591
- distinct_clause = distinct.empty? ? "DISTINCT" : "DISTINCT ON (#{column_list(distinct)})"
592
+ distinct_clause = distinct.empty? ? "DISTINCT" : "DISTINCT ON (#{expression_list(distinct)})"
592
593
  sql = "SELECT #{distinct_clause} #{select_columns}"
593
594
  else
594
595
  sql = "SELECT #{select_columns}"
@@ -607,7 +608,7 @@ module Sequel
607
608
  end
608
609
 
609
610
  if group = opts[:group]
610
- sql << " GROUP BY #{column_list(group)}"
611
+ sql << " GROUP BY #{expression_list(group)}"
611
612
  end
612
613
 
613
614
  if having = opts[:having]
@@ -615,7 +616,7 @@ module Sequel
615
616
  end
616
617
 
617
618
  if order = opts[:order]
618
- sql << " ORDER BY #{column_list(order)}"
619
+ sql << " ORDER BY #{expression_list(order)}"
619
620
  end
620
621
 
621
622
  if limit = opts[:limit]
@@ -694,7 +695,7 @@ module Sequel
694
695
  #
695
696
  # Raises an error if the dataset is grouped or includes more
696
697
  # than one table.
697
- def update_sql(values = {}, opts = nil, &block)
698
+ def update_sql(values = {}, opts = nil)
698
699
  opts = opts ? @opts.merge(opts) : @opts
699
700
 
700
701
  if opts[:group]
@@ -704,23 +705,19 @@ module Sequel
704
705
  end
705
706
 
706
707
  sql = "UPDATE #{source_list(@opts[:from])} SET "
707
- if block
708
- sql << block.to_sql(self, :comma_separated => true)
708
+ set = if values.is_a?(Hash)
709
+ # get values from hash
710
+ values = transform_save(values) if @transform
711
+ values.map do |k, v|
712
+ # convert string key into symbol
713
+ k = k.to_sym if String === k
714
+ "#{literal(k)} = #{literal(v)}"
715
+ end.join(COMMA_SEPARATOR)
709
716
  else
710
- set = if values.is_a?(Hash)
711
- # get values from hash
712
- values = transform_save(values) if @transform
713
- values.map do |k, v|
714
- # convert string key into symbol
715
- k = k.to_sym if String === k
716
- "#{literal(k)} = #{literal(v)}"
717
- end.join(COMMA_SEPARATOR)
718
- else
719
- # copy values verbatim
720
- values
721
- end
722
- sql << set
717
+ # copy values verbatim
718
+ values
723
719
  end
720
+ sql << set
724
721
  if where = opts[:where]
725
722
  sql << " WHERE #{literal(where)}"
726
723
  end
@@ -750,12 +747,18 @@ module Sequel
750
747
  WILDCARD
751
748
  else
752
749
  m = columns.map do |i|
753
- i.is_a?(Hash) ? i.map{|kv| "#{literal(kv[0])} AS #{quote_identifier(kv[1])}"} : literal(i)
750
+ i.is_a?(Hash) ? i.map{|k, v| "#{literal(k)} AS #{quote_identifier(v)}"} : literal(i)
754
751
  end
755
752
  m.join(COMMA_SEPARATOR)
756
753
  end
757
754
  end
758
755
 
756
+ # Converts an array of expressions into a comma separated string of
757
+ # expressions.
758
+ def expression_list(columns)
759
+ columns.map{|i| literal(i)}.join(COMMA_SEPARATOR)
760
+ end
761
+
759
762
  # SQL fragment based on the expr type. See #filter.
760
763
  def filter_expr(expr = nil, &block)
761
764
  expr = nil if expr == []
@@ -774,7 +777,7 @@ module Sequel
774
777
  SQL::BooleanExpression.from_value_pairs(expr)
775
778
  end
776
779
  when Proc
777
- Sequel.use_parse_tree ? expr.to_sql(self).lit : filter_expr(expr.call)
780
+ filter_expr(expr.call(SQL::VirtualRow.new))
778
781
  when SQL::NumericExpression, SQL::StringExpression
779
782
  raise(Error, "Invalid SQL Expression type: #{expr.inspect}")
780
783
  when Symbol, SQL::Expression
@@ -31,7 +31,7 @@ module Sequel
31
31
  # Arguments:
32
32
  # * dataset - Can be a symbol (specifying a table), another dataset,
33
33
  # or an object that responds to .dataset and yields a symbol or a dataset
34
- # * join_conditions - A conditions hash that is passed to the join_table method
34
+ # * join_conditions - Any condition(s) allowed by join_table.
35
35
  # * options - A hash of graph options. The following options are currently used:
36
36
  # * :table_alias - The alias to use for the table. If not specified, doesn't
37
37
  # alias the table. You will get an error if the the alias (or table) name is
@@ -43,7 +43,8 @@ module Sequel
43
43
  # columns and is like simply joining the tables, though graph keeps
44
44
  # some metadata about join that makes it important to use graph instead
45
45
  # of join.
46
- def graph(dataset, join_conditions, options = {})
46
+ # * block - A block that is passed to join_table.
47
+ def graph(dataset, join_conditions = nil, options = {}, &block)
47
48
  # Allow the use of a model, dataset, or symbol as the first argument
48
49
  # Find the table name/dataset based on the argument
49
50
  dataset = dataset.dataset if dataset.respond_to?(:dataset)
@@ -68,7 +69,7 @@ module Sequel
68
69
  raise_alias_error.call if @opts[:graph] && @opts[:graph][:table_aliases] && @opts[:graph][:table_aliases].include?(table_alias)
69
70
 
70
71
  # Join the table early in order to avoid cloning the dataset twice
71
- ds = join_table(options[:join_type] || :left_outer, table, join_conditions, table_alias)
72
+ ds = join_table(options[:join_type] || :left_outer, table, join_conditions, table_alias, &block)
72
73
  opts = ds.opts
73
74
 
74
75
  # Whether to include the table in the result set
@@ -87,15 +88,15 @@ module Sequel
87
88
  # Associates table alias (the master is never aliased)
88
89
  table_aliases = graph[:table_aliases] = {master=>self}
89
90
  # Keep track of the alias numbers used
90
- ca_num = graph[:column_alias_num] = {}
91
+ ca_num = graph[:column_alias_num] = Hash.new(0)
91
92
  # All columns in the master table are never
92
93
  # aliased, but are not included if set_graph_aliases
93
94
  # has been used.
94
95
  if add_columns
95
- select = (opts[:select] ||= [])
96
+ select = opts[:select] = []
96
97
  columns.each do |column|
97
98
  column_aliases[column] = [master, column]
98
- select.push(:"#{master}__#{column}")
99
+ select.push(column.qualify(master))
99
100
  end
100
101
  end
101
102
  end
@@ -120,23 +121,19 @@ module Sequel
120
121
  # using the next value of N that we know hasn't been
121
122
  # used
122
123
  cols.each do |column|
123
- col_alias, c = if column_aliases[column]
124
- tc = :"#{table_alias}_#{column}"
125
- if column_aliases[tc]
126
- if can = ca_num[tc]
127
- ca_num[tc] += 1
128
- tc = :"#{tc}_#{can}"
129
- else
130
- ca_num[tc] = 1
131
- tc = :"#{tc}_0"
132
- end
124
+ col_alias, identifier = if column_aliases[column]
125
+ column_alias = :"#{table_alias}_#{column}"
126
+ if column_aliases[column_alias]
127
+ column_alias_num = ca_num[column_alias]
128
+ column_alias = :"#{column_alias}_#{column_alias_num}"
129
+ ca_num[column_alias] += 1
133
130
  end
134
- [tc, :"#{table_alias}__#{column}___#{tc}"]
131
+ [column_alias, column.qualify(table_alias).as(column_alias)]
135
132
  else
136
- [column, :"#{table_alias}__#{column}"]
133
+ [column, column.qualify(table_alias)]
137
134
  end
138
135
  column_aliases[col_alias] = [table_alias, column]
139
- select.push(c)
136
+ select.push(identifier)
140
137
  end
141
138
  end
142
139
  ds
@@ -158,8 +155,13 @@ module Sequel
158
155
  # The first element of the array should be the table alias,
159
156
  # and the second should be the actual column name.
160
157
  def set_graph_aliases(graph_aliases)
161
- ds = select(*graph_aliases.collect{|col_alias, tc| :"#{tc[0]}__#{tc[1]}#{"___#{col_alias}" unless tc[1] == col_alias}"})
162
- ds.opts[:graph_aliases]=graph_aliases
158
+ cols = graph_aliases.collect do |col_alias, tc|
159
+ identifier = tc[1].qualify(tc[0])
160
+ identifier = identifier.as(col_alias) unless tc[1] == col_alias
161
+ identifier
162
+ end
163
+ ds = select(*cols)
164
+ ds.opts[:graph_aliases] = graph_aliases
163
165
  ds
164
166
  end
165
167
 
@@ -227,6 +227,8 @@ module Sequel
227
227
  # integer, string, date, datetime, boolean, and float.
228
228
  def schema_column_type(db_type)
229
229
  case db_type
230
+ when 'tinyint'
231
+ Sequel.convert_tinyint_to_bool ? :boolean : :integer
230
232
  when /\A(int(eger)?|bigint|smallint)\z/
231
233
  :integer
232
234
  when /\A(character( varying)?|varchar|text)\z/
@@ -237,12 +239,14 @@ module Sequel
237
239
  :datetime
238
240
  when /\Atime( with(out)? time zone)?\z/
239
241
  :time
240
- when /\A(boolean|tinyint)\z/
242
+ when "boolean"
241
243
  :boolean
242
244
  when /\A(real|float|double( precision)?)\z/
243
245
  :float
244
246
  when /\A(numeric|decimal|money)\z/
245
247
  :decimal
248
+ when "bytea"
249
+ :blob
246
250
  end
247
251
  end
248
252
 
@@ -197,6 +197,14 @@ module Sequel
197
197
  end
198
198
  end
199
199
 
200
+ # Includes a method that returns Identifiers.
201
+ module IdentifierMethods
202
+ # Return self wrapped as an identifier.
203
+ def identifier
204
+ Identifier.new(self)
205
+ end
206
+ end
207
+
200
208
  # This module includes the methods that are defined on objects that can be
201
209
  # used in a numeric or string context in SQL (Symbol, LiteralString,
202
210
  # SQL::Function, and SQL::StringExpression).
@@ -385,7 +393,7 @@ module Sequel
385
393
  # a keyword in ruby.
386
394
  attr_reader :aliaz
387
395
 
388
- # default value.
396
+ # Create an object with the given expression and alias.
389
397
  def initialize(expression, aliaz)
390
398
  @expression, @aliaz = expression, aliaz
391
399
  end
@@ -397,13 +405,25 @@ module Sequel
397
405
  end
398
406
  end
399
407
 
408
+ # Blob is used to represent binary data in the Ruby environment that is
409
+ # stored as a blob type in the database. In PostgreSQL, the blob type is
410
+ # called bytea. Sequel represents binary data as a Blob object because
411
+ # certain database engines, such as PostgreSQL, require binary data to be
412
+ # escaped.
413
+ class Blob < ::String
414
+ # return self.
415
+ def to_blob
416
+ self
417
+ end
418
+ end
419
+
400
420
  # Subclass of ComplexExpression where the expression results
401
421
  # in a boolean value in SQL.
402
422
  class BooleanExpression < ComplexExpression
403
423
  include BooleanMethods
404
424
 
405
425
  # Take pairs of values (e.g. a hash or array of arrays of two pairs)
406
- # and converts it to a ComplexExpression. The operator and args
426
+ # and converts it to a BooleanExpression. The operator and args
407
427
  # used depends on the case of the right (2nd) argument:
408
428
  #
409
429
  # * 0..10 - left >= 0 AND left <= 10
@@ -531,6 +551,27 @@ module Sequel
531
551
  end
532
552
  end
533
553
 
554
+ # Represents an identifier (column or table). Can be used
555
+ # to specify a Symbol with multiple underscores should not be
556
+ # split, or for creating an identifier without using a symbol.
557
+ class Identifier < GenericExpression
558
+ include QualifyingMethods
559
+
560
+ # The table and column to reference
561
+ attr_reader :value
562
+
563
+ # Set the value to the given argument
564
+ def initialize(value)
565
+ @value = value
566
+ end
567
+
568
+ # Delegate the creation of the resulting SQL to the given dataset,
569
+ # since it may be database dependent.
570
+ def to_s(ds)
571
+ ds.quote_identifier(@value)
572
+ end
573
+ end
574
+
534
575
  # IrregularFunction is used for the SQL EXTRACT and CAST functions,
535
576
  # which don't use regular function calling syntax. The IrregularFunction
536
577
  # replaces the commas the regular function uses with a custom
@@ -637,10 +678,10 @@ module Sequel
637
678
  # The expression to order the result set by.
638
679
  attr_reader :expression
639
680
 
640
- # Whether the expression should order the result set in a descening manner
681
+ # Whether the expression should order the result set in a descending manner
641
682
  attr_reader :descending
642
683
 
643
- # default value.
684
+ # Set the expression and descending attributes to the given values.
644
685
  def initialize(expression, descending = true)
645
686
  @expression, @descending = expression, descending
646
687
  end
@@ -727,9 +768,39 @@ module Sequel
727
768
  ds.subscript_sql(self)
728
769
  end
729
770
  end
771
+
772
+ # An instance of this class is yielded to the block supplied to filter.
773
+ # Useful if another library also defines the operator methods that
774
+ # Sequel defines for symbols.
775
+ #
776
+ # Examples:
777
+ #
778
+ # ds = DB[:t]
779
+ # ds.filter{|r| r.name < 2} # SELECT * FROM t WHERE (name < 2)
780
+ # ds.filter{|r| r.table__column + 1 < 2} # SELECT * FROM t WHERE ((table.column + 1) < 2)
781
+ # ds.filter{|r| r.is_active(1, 'arg2')} # SELECT * FROM t WHERE is_active(1, 'arg2')
782
+ class VirtualRow
783
+ (instance_methods - %w"__id__ __send__").each{|m| undef_method(m)}
784
+
785
+ # Can return Identifiers, QualifiedIdentifiers, or Functions:
786
+ #
787
+ # * Function - returned if any arguments are supplied, using the method name
788
+ # as the function name, and the arguments as the function arguments.
789
+ # * QualifiedIdentifier - returned if the method name contains __, with the
790
+ # table being the part before __, and the column being the part after.
791
+ # * Identifier - returned otherwise, using the method name.
792
+ def method_missing(m, *args)
793
+ if args.empty?
794
+ table, column = m.to_s.split('__', 2)
795
+ column ? QualifiedIdentifier.new(table, column) : Identifier.new(m)
796
+ else
797
+ Function.new(m, *args)
798
+ end
799
+ end
800
+ end
730
801
  end
731
802
 
732
- # LiteralString is used to represent literal SQL expressions. An
803
+ # LiteralString is used to represent literal SQL expressions. A
733
804
  # LiteralString is copied verbatim into an SQL statement. Instances of
734
805
  # LiteralString can be created by calling String#lit.
735
806
  # LiteralStrings can use all of the SQL::ColumnMethods and the