sequel_core 2.1.0 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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